PrEditor 0.0.0.dev1__py2.py3-none-any.whl → 0.1.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (363) hide show
  1. PrEditor-0.1.0.dist-info/LICENSE +165 -0
  2. PrEditor-0.1.0.dist-info/METADATA +212 -0
  3. PrEditor-0.1.0.dist-info/RECORD +149 -0
  4. {PrEditor-0.0.0.dev1.dist-info → PrEditor-0.1.0.dist-info}/WHEEL +1 -1
  5. PrEditor-0.1.0.dist-info/entry_points.txt +18 -0
  6. PrEditor-0.1.0.dist-info/top_level.txt +1 -0
  7. preditor/__init__.py +301 -0
  8. {blurdev → preditor}/__main__.py +13 -13
  9. preditor/about_module.py +166 -0
  10. preditor/cli.py +192 -0
  11. {blurdev → preditor}/contexts.py +119 -119
  12. preditor/cores/core.py +65 -0
  13. preditor/dccs/maya/PrEditor_maya.mod +2 -0
  14. preditor/dccs/maya/plug-ins/PrEditor_maya.py +108 -0
  15. preditor/debug.py +294 -0
  16. blurdev/scintilla/delayable_engine.py → preditor/delayable_engine/__init__.py +310 -299
  17. blurdev/scintilla/delayables/base.py → preditor/delayable_engine/delayables.py +85 -85
  18. {blurdev → preditor}/enum.py +728 -1003
  19. {blurdev → preditor}/gui/__init__.py +84 -125
  20. preditor/gui/app.py +159 -0
  21. {blurdev → preditor}/gui/codehighlighter.py +209 -219
  22. {blurdev → preditor}/gui/completer.py +226 -236
  23. {blurdev → preditor}/gui/console.py +801 -858
  24. {blurdev → preditor}/gui/dialog.py +200 -216
  25. preditor/gui/drag_tab_bar.py +190 -0
  26. preditor/gui/editor_chooser.py +57 -0
  27. {blurdev → preditor}/gui/errordialog.py +100 -97
  28. preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
  29. preditor/gui/group_tab_widget/__init__.py +319 -0
  30. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  31. preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
  32. preditor/gui/group_tab_widget/grouped_tab_widget.py +75 -0
  33. preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
  34. preditor/gui/level_buttons.py +349 -0
  35. {blurdev → preditor}/gui/logger_window_handler.py +46 -45
  36. {blurdev → preditor}/gui/loggerwindow.py +1205 -1417
  37. {blurdev → preditor}/gui/newtabwidget.py +69 -68
  38. {blurdev → preditor}/gui/redmine_login_dialog.py +63 -61
  39. {blurdev → preditor}/gui/set_text_editor_path_dialog.py +59 -57
  40. preditor/gui/ui/editor_chooser.ui +93 -0
  41. {blurdev → preditor}/gui/ui/errordialog.ui +81 -81
  42. {blurdev → preditor}/gui/ui/loggerwindow.ui +1030 -864
  43. {blurdev → preditor}/gui/ui/redmine_login_dialog.ui +124 -124
  44. {blurdev → preditor}/gui/ui/set_text_editor_path_dialog.ui +149 -149
  45. {blurdev → preditor}/gui/window.py +183 -199
  46. preditor/gui/workbox_mixin.py +357 -0
  47. preditor/gui/workbox_text_edit.py +117 -0
  48. preditor/gui/workboxwidget.py +276 -0
  49. preditor/logging_config.py +52 -0
  50. preditor/osystem.py +401 -0
  51. preditor/plugins.py +65 -0
  52. preditor/prefs.py +74 -0
  53. {blurdev → preditor}/resource/environment_variables.html +26 -38
  54. {blurdev → preditor}/resource/error_mail.html +85 -85
  55. {blurdev → preditor}/resource/error_mail_inline.html +41 -41
  56. preditor/resource/img/README.md +7 -0
  57. preditor/resource/img/arrow_forward.png +0 -0
  58. preditor/resource/img/check-bold.png +0 -0
  59. preditor/resource/img/chevron-down.png +0 -0
  60. preditor/resource/img/chevron-up.png +0 -0
  61. preditor/resource/img/close-thick.png +0 -0
  62. preditor/resource/img/comment-edit.png +0 -0
  63. preditor/resource/img/content-copy.png +0 -0
  64. preditor/resource/img/content-cut.png +0 -0
  65. preditor/resource/img/content-duplicate.png +0 -0
  66. preditor/resource/img/content-paste.png +0 -0
  67. preditor/resource/img/content-save.png +0 -0
  68. preditor/resource/img/debug_disabled.png +0 -0
  69. preditor/resource/img/eye-check.png +0 -0
  70. preditor/resource/img/file-plus.png +0 -0
  71. preditor/resource/img/file-remove.png +0 -0
  72. preditor/resource/img/format-align-left.png +0 -0
  73. preditor/resource/img/format-letter-case-lower.png +0 -0
  74. preditor/resource/img/format-letter-case-upper.png +0 -0
  75. preditor/resource/img/information.png +0 -0
  76. preditor/resource/img/logging_critical.png +0 -0
  77. preditor/resource/img/logging_custom.png +0 -0
  78. preditor/resource/img/logging_debug.png +0 -0
  79. preditor/resource/img/logging_error.png +0 -0
  80. preditor/resource/img/logging_info.png +0 -0
  81. preditor/resource/img/logging_not_set.png +0 -0
  82. preditor/resource/img/logging_warning.png +0 -0
  83. preditor/resource/img/marker.png +0 -0
  84. preditor/resource/img/play.png +0 -0
  85. preditor/resource/img/playlist-play.png +0 -0
  86. preditor/resource/img/plus-minus-variant.png +0 -0
  87. preditor/resource/img/preditor.ico +0 -0
  88. preditor/resource/img/preditor.png +0 -0
  89. preditor/resource/img/preditor.psd +0 -0
  90. preditor/resource/img/preditor.svg +44 -0
  91. preditor/resource/img/restart.svg +1 -0
  92. preditor/resource/img/skip-forward-outline.png +0 -0
  93. preditor/resource/img/skip-next-outline.png +0 -0
  94. preditor/resource/img/skip-next.png +0 -0
  95. preditor/resource/img/skip-previous.png +0 -0
  96. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  97. preditor/resource/img/text-search-variant.png +0 -0
  98. {blurdev → preditor}/resource/lang/python.json +30 -30
  99. preditor/resource/settings.ini +25 -0
  100. {blurdev/resource/stylesheet/logger → preditor/resource/stylesheet}/Bright.css +56 -61
  101. {blurdev → preditor}/resource/stylesheet/Dark.css +190 -132
  102. {blurdev → preditor}/scintilla/__init__.py +22 -28
  103. preditor/scintilla/delayables/__init__.py +11 -0
  104. {blurdev → preditor}/scintilla/delayables/smart_highlight.py +94 -93
  105. {blurdev → preditor}/scintilla/delayables/spell_check.py +173 -172
  106. {blurdev → preditor}/scintilla/documenteditor.py +2039 -2115
  107. {blurdev → preditor}/scintilla/finddialog.py +68 -81
  108. {blurdev → preditor}/scintilla/lang/__init__.py +80 -93
  109. {blurdev → preditor}/scintilla/lang/config/bash.ini +15 -15
  110. {blurdev → preditor}/scintilla/lang/config/batch.ini +14 -14
  111. {blurdev → preditor}/scintilla/lang/config/cpp.ini +19 -19
  112. {blurdev → preditor}/scintilla/lang/config/css.ini +19 -19
  113. {blurdev → preditor}/scintilla/lang/config/eyeonscript.ini +17 -17
  114. {blurdev → preditor}/scintilla/lang/config/html.ini +21 -21
  115. {blurdev → preditor}/scintilla/lang/config/javascript.ini +24 -24
  116. {blurdev → preditor}/scintilla/lang/config/lua.ini +16 -16
  117. {blurdev → preditor}/scintilla/lang/config/maxscript.ini +20 -20
  118. {blurdev → preditor}/scintilla/lang/config/mel.ini +18 -18
  119. {blurdev → preditor}/scintilla/lang/config/mu.ini +22 -22
  120. {blurdev → preditor}/scintilla/lang/config/nsi.ini +5 -5
  121. {blurdev → preditor}/scintilla/lang/config/perl.ini +19 -19
  122. {blurdev → preditor}/scintilla/lang/config/puppet.ini +19 -19
  123. {blurdev → preditor}/scintilla/lang/config/python.ini +28 -28
  124. {blurdev → preditor}/scintilla/lang/config/ruby.ini +19 -19
  125. {blurdev → preditor}/scintilla/lang/config/sql.ini +7 -7
  126. {blurdev → preditor}/scintilla/lang/config/xml.ini +21 -21
  127. {blurdev → preditor}/scintilla/lang/config/yaml.ini +18 -18
  128. {blurdev → preditor}/scintilla/lang/language.py +240 -250
  129. preditor/scintilla/lexers/__init__.py +0 -0
  130. {blurdev → preditor}/scintilla/lexers/cpplexer.py +21 -30
  131. {blurdev → preditor}/scintilla/lexers/javascriptlexer.py +25 -34
  132. {blurdev → preditor}/scintilla/lexers/maxscriptlexer.py +234 -253
  133. {blurdev → preditor}/scintilla/lexers/mellexer.py +368 -376
  134. {blurdev → preditor}/scintilla/lexers/mulexer.py +32 -41
  135. {blurdev → preditor}/scintilla/lexers/pythonlexer.py +41 -50
  136. {blurdev → preditor}/scintilla/ui/finddialog.ui +160 -160
  137. preditor/settings.py +71 -0
  138. preditor/stream/__init__.py +80 -0
  139. preditor/stream/director.py +56 -0
  140. preditor/stream/manager.py +74 -0
  141. preditor/streamhandler_helper.py +46 -0
  142. preditor/utils/__init__.py +0 -0
  143. preditor/utils/cute.py +30 -0
  144. preditor/utils/stylesheets.py +54 -0
  145. {blurdev → preditor}/version.py +5 -5
  146. preditor/weakref.py +363 -0
  147. PrEditor-0.0.0.dev1.dist-info/METADATA +0 -51
  148. PrEditor-0.0.0.dev1.dist-info/RECORD +0 -279
  149. PrEditor-0.0.0.dev1.dist-info/top_level.txt +0 -1
  150. blurdev/__init__.py +0 -356
  151. blurdev/cores/__init__.py +0 -98
  152. blurdev/cores/application.py +0 -26
  153. blurdev/cores/core.py +0 -634
  154. blurdev/debug.py +0 -593
  155. blurdev/external.py +0 -391
  156. blurdev/gui/level_buttons.py +0 -585
  157. blurdev/gui/workboxwidget.py +0 -205
  158. blurdev/logger.py +0 -238
  159. blurdev/osystem.py +0 -813
  160. blurdev/prefs.py +0 -33
  161. blurdev/protocols/__init__.py +0 -71
  162. blurdev/protocols/write_std_output_handler.py +0 -83
  163. blurdev/resource/designer_plugins.xml +0 -9
  164. blurdev/resource/error_email_old.html +0 -41
  165. blurdev/resource/img/add.png +0 -0
  166. blurdev/resource/img/ajax-loader.gif +0 -0
  167. blurdev/resource/img/application.png +0 -0
  168. blurdev/resource/img/applications.png +0 -0
  169. blurdev/resource/img/assburner.png +0 -0
  170. blurdev/resource/img/assfreezer.png +0 -0
  171. blurdev/resource/img/bar.gif +0 -0
  172. blurdev/resource/img/blank.png +0 -0
  173. blurdev/resource/img/blurdev.png +0 -0
  174. blurdev/resource/img/calendar_disabled.png +0 -0
  175. blurdev/resource/img/calendar_enabled.png +0 -0
  176. blurdev/resource/img/cancel.png +0 -0
  177. blurdev/resource/img/custom.png +0 -0
  178. blurdev/resource/img/debug_high.png +0 -0
  179. blurdev/resource/img/debug_low.png +0 -0
  180. blurdev/resource/img/debug_mid.png +0 -0
  181. blurdev/resource/img/debug_off.png +0 -0
  182. blurdev/resource/img/django.png +0 -0
  183. blurdev/resource/img/doc.png +0 -0
  184. blurdev/resource/img/edit.png +0 -0
  185. blurdev/resource/img/elemental.png +0 -0
  186. blurdev/resource/img/explore.png +0 -0
  187. blurdev/resource/img/favorite.png +0 -0
  188. blurdev/resource/img/file.png +0 -0
  189. blurdev/resource/img/folder.png +0 -0
  190. blurdev/resource/img/ide/add.png +0 -0
  191. blurdev/resource/img/ide/add_note.png +0 -0
  192. blurdev/resource/img/ide/arrow_down.png +0 -0
  193. blurdev/resource/img/ide/arrow_up.png +0 -0
  194. blurdev/resource/img/ide/check.png +0 -0
  195. blurdev/resource/img/ide/class.png +0 -0
  196. blurdev/resource/img/ide/clean.png +0 -0
  197. blurdev/resource/img/ide/clearlog.png +0 -0
  198. blurdev/resource/img/ide/close.png +0 -0
  199. blurdev/resource/img/ide/comment_add.png +0 -0
  200. blurdev/resource/img/ide/comment_remove.png +0 -0
  201. blurdev/resource/img/ide/comment_toggle.png +0 -0
  202. blurdev/resource/img/ide/console.png +0 -0
  203. blurdev/resource/img/ide/copy.png +0 -0
  204. blurdev/resource/img/ide/copylstrip.png +0 -0
  205. blurdev/resource/img/ide/cut.png +0 -0
  206. blurdev/resource/img/ide/edit.png +0 -0
  207. blurdev/resource/img/ide/find.png +0 -0
  208. blurdev/resource/img/ide/find_replace.png +0 -0
  209. blurdev/resource/img/ide/findnext.png +0 -0
  210. blurdev/resource/img/ide/findprev.png +0 -0
  211. blurdev/resource/img/ide/folder_find.png +0 -0
  212. blurdev/resource/img/ide/function.png +0 -0
  213. blurdev/resource/img/ide/git-bash.png +0 -0
  214. blurdev/resource/img/ide/git-gui.png +0 -0
  215. blurdev/resource/img/ide/gitk.png +0 -0
  216. blurdev/resource/img/ide/goto.png +0 -0
  217. blurdev/resource/img/ide/goto_def.png +0 -0
  218. blurdev/resource/img/ide/help.png +0 -0
  219. blurdev/resource/img/ide/highlighter.png +0 -0
  220. blurdev/resource/img/ide/lowercase.png +0 -0
  221. blurdev/resource/img/ide/newfile.png +0 -0
  222. blurdev/resource/img/ide/newfolder.png +0 -0
  223. blurdev/resource/img/ide/newproject.png +0 -0
  224. blurdev/resource/img/ide/newwizard.png +0 -0
  225. blurdev/resource/img/ide/open.png +0 -0
  226. blurdev/resource/img/ide/paste.png +0 -0
  227. blurdev/resource/img/ide/pdb_continue.png +0 -0
  228. blurdev/resource/img/ide/pdb_down.png +0 -0
  229. blurdev/resource/img/ide/pdb_next.png +0 -0
  230. blurdev/resource/img/ide/pdb_step.png +0 -0
  231. blurdev/resource/img/ide/pdb_up.png +0 -0
  232. blurdev/resource/img/ide/plus_minus.png +0 -0
  233. blurdev/resource/img/ide/preferences.png +0 -0
  234. blurdev/resource/img/ide/project_find.png +0 -0
  235. blurdev/resource/img/ide/python.png +0 -0
  236. blurdev/resource/img/ide/pyular.png +0 -0
  237. blurdev/resource/img/ide/qt.png +0 -0
  238. blurdev/resource/img/ide/quit.png +0 -0
  239. blurdev/resource/img/ide/redo.png +0 -0
  240. blurdev/resource/img/ide/refresh.png +0 -0
  241. blurdev/resource/img/ide/remove.png +0 -0
  242. blurdev/resource/img/ide/ruler.png +0 -0
  243. blurdev/resource/img/ide/run.png +0 -0
  244. blurdev/resource/img/ide/runall.png +0 -0
  245. blurdev/resource/img/ide/runallclear.png +0 -0
  246. blurdev/resource/img/ide/runselected.png +0 -0
  247. blurdev/resource/img/ide/runselectedclear.png +0 -0
  248. blurdev/resource/img/ide/save.png +0 -0
  249. blurdev/resource/img/ide/saveas.png +0 -0
  250. blurdev/resource/img/ide/sdk.png +0 -0
  251. blurdev/resource/img/ide/separator.png +0 -0
  252. blurdev/resource/img/ide/tabbed.png +0 -0
  253. blurdev/resource/img/ide/tile.png +0 -0
  254. blurdev/resource/img/ide/toolbar.png +0 -0
  255. blurdev/resource/img/ide/undo.png +0 -0
  256. blurdev/resource/img/ide/uppercase.png +0 -0
  257. blurdev/resource/img/ide/view_as.png +0 -0
  258. blurdev/resource/img/ide/windowed.png +0 -0
  259. blurdev/resource/img/ide.ico +0 -0
  260. blurdev/resource/img/ide.png +0 -0
  261. blurdev/resource/img/ide48.png +0 -0
  262. blurdev/resource/img/info.png +0 -0
  263. blurdev/resource/img/legacy tool.png +0 -0
  264. blurdev/resource/img/library.png +0 -0
  265. blurdev/resource/img/logger/about.png +0 -0
  266. blurdev/resource/img/logger/arrow_forward.png +0 -0
  267. blurdev/resource/img/logger/clear.png +0 -0
  268. blurdev/resource/img/logger/close.png +0 -0
  269. blurdev/resource/img/logger/debug_disabled.png +0 -0
  270. blurdev/resource/img/logger/debug_high.png +0 -0
  271. blurdev/resource/img/logger/debug_low.png +0 -0
  272. blurdev/resource/img/logger/debug_mid.png +0 -0
  273. blurdev/resource/img/logger/down.png +0 -0
  274. blurdev/resource/img/logger/find.png +0 -0
  275. blurdev/resource/img/logger/logging_critical.png +0 -0
  276. blurdev/resource/img/logger/logging_debug.png +0 -0
  277. blurdev/resource/img/logger/logging_error.png +0 -0
  278. blurdev/resource/img/logger/logging_info.png +0 -0
  279. blurdev/resource/img/logger/logging_not_set.png +0 -0
  280. blurdev/resource/img/logger/logging_warning.png +0 -0
  281. blurdev/resource/img/logger/next.png +0 -0
  282. blurdev/resource/img/logger/play.png +0 -0
  283. blurdev/resource/img/logger/playlist_play.png +0 -0
  284. blurdev/resource/img/logger/previous.png +0 -0
  285. blurdev/resource/img/logger/return.png +0 -0
  286. blurdev/resource/img/logger/save.png +0 -0
  287. blurdev/resource/img/logger/subdirectory_arrow_right.png +0 -0
  288. blurdev/resource/img/logger/up.png +0 -0
  289. blurdev/resource/img/lovebar.png +0 -0
  290. blurdev/resource/img/new.png +0 -0
  291. blurdev/resource/img/new_selected.png +0 -0
  292. blurdev/resource/img/node.png +0 -0
  293. blurdev/resource/img/ok.png +0 -0
  294. blurdev/resource/img/options.png +0 -0
  295. blurdev/resource/img/packages.png +0 -0
  296. blurdev/resource/img/preview/add.png +0 -0
  297. blurdev/resource/img/preview/brush.png +0 -0
  298. blurdev/resource/img/preview/delete.png +0 -0
  299. blurdev/resource/img/preview/delte.png +0 -0
  300. blurdev/resource/img/preview/fill.png +0 -0
  301. blurdev/resource/img/preview/layers.png +0 -0
  302. blurdev/resource/img/preview/media.png +0 -0
  303. blurdev/resource/img/preview/navigate.png +0 -0
  304. blurdev/resource/img/preview/pencil.png +0 -0
  305. blurdev/resource/img/preview/select.png +0 -0
  306. blurdev/resource/img/preview/type.png +0 -0
  307. blurdev/resource/img/preview/visible.png +0 -0
  308. blurdev/resource/img/project.png +0 -0
  309. blurdev/resource/img/python_logger.ico +0 -0
  310. blurdev/resource/img/python_logger.png +0 -0
  311. blurdev/resource/img/refresh.png +0 -0
  312. blurdev/resource/img/remove.png +0 -0
  313. blurdev/resource/img/reset.png +0 -0
  314. blurdev/resource/img/richtext/font_bold.png +0 -0
  315. blurdev/resource/img/richtext/font_italic.png +0 -0
  316. blurdev/resource/img/richtext/link_image.png +0 -0
  317. blurdev/resource/img/richtext/spell_check.png +0 -0
  318. blurdev/resource/img/richtext/unordered_list.png +0 -0
  319. blurdev/resource/img/save.png +0 -0
  320. blurdev/resource/img/savesettings.png +0 -0
  321. blurdev/resource/img/settings.png +0 -0
  322. blurdev/resource/img/tool.png +0 -0
  323. blurdev/resource/img/toolbarHandleHorizontal.png +0 -0
  324. blurdev/resource/img/toolbarHandleVertical.png +0 -0
  325. blurdev/resource/img/trash.png +0 -0
  326. blurdev/resource/img/trax.png +0 -0
  327. blurdev/resource/img/tree.png +0 -0
  328. blurdev/resource/img/treegrunt.ico +0 -0
  329. blurdev/resource/img/treegrunt.png +0 -0
  330. blurdev/resource/img/treegruntedit.png +0 -0
  331. blurdev/resource/img/user interface.png +0 -0
  332. blurdev/resource/img/warning.png +0 -0
  333. blurdev/resource/img/watermark.png +0 -0
  334. blurdev/resource/sdk/blurdev.sdk +0 -3
  335. blurdev/resource/settings.ini +0 -82
  336. blurdev/resource/softimage/BlurApplication.dll +0 -0
  337. blurdev/resource/softimage/BlurApplication64.dll +0 -0
  338. blurdev/resource/stylesheet/Carbon.css +0 -35
  339. blurdev/resource/stylesheet/logger/Dark.css +0 -62
  340. blurdev/resource/templ/py_comment.templ +0 -1
  341. blurdev/resource/templ/py_debug_raise_error.templ +0 -7
  342. blurdev/resource/templ/py_doc_string.templ +0 -10
  343. blurdev/resource/templ/py_header.templ +0 -9
  344. blurdev/resource/templ/py_line_comment.templ +0 -1
  345. blurdev/resource/templ/py_log_to_file.templ +0 -22
  346. blurdev/resource/templ/py_module_path.templ +0 -1
  347. blurdev/resource/templ/py_pyqt_core.templ +0 -1
  348. blurdev/resource/templ/py_pyqt_gui.templ +0 -1
  349. blurdev/resource/templ/py_splashscreen.templ +0 -6
  350. blurdev/resource/templ/py_testing_note.templ +0 -1
  351. blurdev/resource/templ/py_testing_note_end.templ +0 -1
  352. blurdev/resource/tools_environments.json +0 -72
  353. blurdev/resource/tools_environments.xml +0 -11
  354. blurdev/resource/tools_environments_linux.xml +0 -11
  355. blurdev/resource/tools_environments_offline.xml +0 -7
  356. blurdev/runtimes/__init__.py +0 -2
  357. blurdev/runtimes/logger.py +0 -44
  358. blurdev/scintilla/delayables/__init__.py +0 -9
  359. blurdev/settings.py +0 -312
  360. blurdev/utils/error.py +0 -389
  361. {blurdev/scintilla/lexers → preditor/cores}/__init__.py +0 -0
  362. {blurdev/utils → preditor/gui/fuzzy_search}/__init__.py +0 -0
  363. {blurdev → preditor}/resource/img/warning-big.png +0 -0
@@ -1,858 +1,801 @@
1
- """ LoggerWindow class is an overloaded python interpreter for blurdev
2
-
3
- """
4
-
5
- from __future__ import print_function
6
- from __future__ import absolute_import
7
- import __main__
8
- import os
9
- import re
10
- import string
11
- import subprocess
12
- import sys
13
- import time
14
- import traceback
15
-
16
- from Qt import QtCompat
17
- from Qt.QtCore import QPoint, Qt
18
- from Qt.QtGui import QColor, QFontMetrics, QTextCharFormat, QTextCursor, QTextDocument
19
- from Qt.QtWidgets import QAction, QApplication, QTextEdit
20
-
21
- import blurdev
22
- from blurdev import debug
23
- from blurdev.debug import BlurExcepthook
24
- from .completer import PythonCompleter
25
- from builtins import str as text
26
- from .codehighlighter import CodeHighlighter
27
- from pillar import stream
28
- from pillar.streamhandler_helper import StreamHandlerHelper
29
-
30
-
31
- class ConsoleEdit(QTextEdit):
32
- # Ensure the error prompt only shows up once.
33
- _errorPrompted = False
34
- # the color error messages are displayed in, can be set by stylesheets
35
- _errorMessageColor = QColor(Qt.red)
36
-
37
- def __init__(self, parent):
38
- super(QTextEdit, self).__init__(parent)
39
- # store the error buffer
40
- self._completer = None
41
-
42
- # If populated, also write to this interface
43
- self.outputPipe = None
44
-
45
- self._stdoutColor = QColor(17, 154, 255)
46
- self._commentColor = QColor(0, 206, 52)
47
- self._keywordColor = QColor(17, 154, 255)
48
- self._stringColor = QColor(255, 128, 0)
49
- self._resultColor = QColor(128, 128, 128)
50
-
51
- # These variables are used to enable pdb mode. This is a special mode used by
52
- # the logger if it is launched externally via getPdb, set_trace, or post_mortem
53
- # in blurdev.debug.
54
- self._pdbPrompt = '(Pdb) '
55
- self._consolePrompt = '>>> '
56
- # Note: Changing _outputPrompt may require updating resource\lang\python.xml
57
- # If still using a #
58
- self._outputPrompt = '#Result: '
59
- self._pdbMode = False
60
- # if populated when setPdbMode is called, this action will be enabled and its
61
- # check state will match the current pdbMode.
62
- self.pdbModeAction = None
63
- # Method used to update the gui when pdb mode changes
64
- self.pdbUpdateVisibility = None
65
- # Method used to update the gui when code is executed
66
- self.reportExecutionTime = None
67
-
68
- self._firstShow = True
69
-
70
- # When executing code, that takes longer than this seconds, flash the window
71
- self.flashTime = 1.0
72
-
73
- # Store previous commands to retrieve easily
74
- self._prevCommands = []
75
- self._prevCommandIndex = 0
76
- self._prevCommandsMax = 100
77
-
78
- # create the completer
79
- self.setCompleter(PythonCompleter(self))
80
-
81
- # sys.__stdout__ doesn't work if some third party has implemented their own
82
- # override. Use these to backup the current logger so the logger displays
83
- # output, but application specific consoles also get the info.
84
- self.stdout = None
85
- self.stderr = None
86
- self._errorLog = None
87
- # overload the sys logger (if we are not on a high debugging level)
88
- if (
89
- os.path.basename(sys.executable) != 'python.exe'
90
- or debug.debugLevel() != debug.DebugLevel.High
91
- ):
92
- self.stream_manager = stream.install_to_std()
93
- # Redirect future writes directly to the console, add any previous writes
94
- # to the console and free up the memory consumed by previous writes as we
95
- # assume this is likely to be the only callback added to the manager.
96
- self.stream_manager.add_callback(
97
- self.write, replay=True, disable_writes=True, clear=True
98
- )
99
- # Store the current outputs
100
- self.stdout = sys.stdout
101
- self.stderr = sys.stderr
102
- self._errorLog = sys.stderr
103
- BlurExcepthook.install()
104
-
105
- # Update any StreamHandler's that were setup using the old stdout/err
106
- StreamHandlerHelper.replace_stream(self.stdout, sys.stdout)
107
- StreamHandlerHelper.replace_stream(self.stderr, sys.stderr)
108
-
109
- # create the highlighter
110
- highlight = CodeHighlighter(self)
111
- highlight.setLanguage('Python')
112
- self.uiCodeHighlighter = highlight
113
-
114
- self.uiClearToLastPromptACT = QAction('Clear to Last', self)
115
- self.uiClearToLastPromptACT.triggered.connect(self.clearToLastPrompt)
116
- self.uiClearToLastPromptACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_Backspace)
117
- self.addAction(self.uiClearToLastPromptACT)
118
-
119
- self.x = 0
120
- self.clickPos = None
121
- self.anchor = None
122
-
123
- def setConsoleFont(self, font):
124
- """Set the console's font and adjust the tabStopWidth"""
125
- self.setFont(font)
126
-
127
- # Set the setTabStopWidth for the console's font
128
- tabWidth = 4
129
- if hasattr(self, "window") and "LoggerWindow" in str(type(self.window())):
130
- tabWidth = self.window().uiWorkboxTAB.widget(0).tabWidth()
131
- fontPixelWidth = QFontMetrics(font).width(" ")
132
- self.setTabStopWidth(fontPixelWidth * tabWidth)
133
-
134
- def mousePressEvent(self, event):
135
- """Overload of mousePressEvent to capture click position, so on release, we can
136
- check release position. If it's the same (ie user clicked vs click-drag to
137
- select text), we check if user clicked an error hyperlink.
138
- """
139
- self.clickPos = event.pos()
140
- self.anchor = self.anchorAt(event.pos())
141
- if self.anchor:
142
- QApplication.setOverrideCursor(Qt.PointingHandCursor)
143
- return super(ConsoleEdit, self).mousePressEvent(event)
144
-
145
- def mouseReleaseEvent(self, event):
146
- """Overload of mouseReleaseEvent to capture if user has left clicked... Check if
147
- click position is the same as release position, if so, call errorHyperlink.
148
- """
149
- samePos = event.pos() == self.clickPos
150
- left = event.button() == Qt.LeftButton
151
- if samePos and left and self.anchor:
152
- self.errorHyperlink()
153
-
154
- self.clickPos = None
155
- self.anchor = None
156
- QApplication.restoreOverrideCursor()
157
- return super(ConsoleEdit, self).mouseReleaseEvent(event)
158
-
159
- def wheelEvent(self, event):
160
- """Override of wheelEvent to allow for font resizing by holding ctrl while"""
161
- # scrolling. If used in LoggerWindow, use that wheel event
162
- # May not want to import LoggerWindow, so perhaps
163
- # check by str(type())
164
- ctrlPressed = event.modifiers() == Qt.ControlModifier
165
- if ctrlPressed and "LoggerWindow" in str(type(self.window())):
166
- self.window().wheelEvent(event)
167
- else:
168
- QTextEdit.wheelEvent(self, event)
169
-
170
- def keyReleaseEvent(self, event):
171
- """Override of keyReleaseEvent to determine when to end navigation of
172
- previous commands
173
- """
174
- if event.key() == Qt.Key_Alt:
175
- self._prevCommandIndex = 0
176
- else:
177
- event.ignore()
178
-
179
- def errorHyperlink(self):
180
- """Determine if chosen line is an error traceback file-info line, if so, parse
181
- the filepath and line number, and attempt to open the module file in the user's
182
- chosen text editor at the relevant line, using specified Command Prompt pattern.
183
-
184
- The text editor defaults to SublimeText3, in the normal install directory
185
- """
186
- window = self.window()
187
-
188
- # Bail if Error Hyperlinks setting is not turned on or we don't have an anchor.
189
- doHyperlink = (
190
- hasattr(window, 'uiErrorHyperlinksACT')
191
- and window.uiErrorHyperlinksACT.isChecked()
192
- and self.anchor
193
- )
194
- if not doHyperlink:
195
- return
196
-
197
- # info is a comma separated string, in the form: "filename, workboxIdx, lineNum"
198
- info = self.anchor.split(', ')
199
- modulePath = info[0]
200
- workboxIndex = info[1]
201
- lineNum = info[2]
202
-
203
- # fetch info from LoggerWindow
204
- exePath = ''
205
- cmdTempl = ''
206
- if hasattr(window, 'textEditorPath'):
207
- exePath = window.textEditorPath
208
- cmdTempl = window.textEditorCmdTempl
209
-
210
- # Bail if not setup properly
211
- msg = "Cannot use traceback hyperlink. "
212
- if not exePath:
213
- msg += "No text editor path defined."
214
- print(msg)
215
- return
216
- if not os.path.exists(exePath):
217
- msg += "Text editor executable does not exist: {}".format(exePath)
218
- print(msg)
219
- return
220
- if not cmdTempl:
221
- msg += "No text editor Command Prompt command template defined."
222
- print(msg)
223
- return
224
- if modulePath and not os.path.exists(modulePath):
225
- msg += "Specified module path does not exist: {}".format(modulePath)
226
- print(msg)
227
- return
228
-
229
- if modulePath:
230
- # Attempt to create command from template and run the command
231
- try:
232
- command = cmdTempl.format(
233
- exePath=exePath, modulePath=modulePath, lineNum=lineNum
234
- )
235
- subprocess.Popen(command)
236
- except (ValueError, OSError):
237
- msg = "The provided text editor command template is not valid:\n {}"
238
- msg = msg.format(cmdTempl)
239
- print(msg)
240
- elif workboxIndex is not None:
241
- workboxIndex = int(workboxIndex)
242
- lineNum = int(lineNum)
243
- window.uiWorkboxTAB.setCurrentIndex(workboxIndex)
244
- workbox = window.uiWorkboxTAB.widget(workboxIndex)
245
- workbox.SendScintilla(workbox.SCI_GOTOLINE, lineNum - 1)
246
- workbox.setFocus()
247
-
248
- def getPrevCommand(self):
249
- """Find and display the previous command in stack"""
250
- self._prevCommandIndex -= 1
251
-
252
- if abs(self._prevCommandIndex) > len(self._prevCommands):
253
- self._prevCommandIndex += 1
254
-
255
- if self._prevCommands:
256
- self.setCommand()
257
-
258
- def getNextCommand(self):
259
- """Find and display the next command in stack"""
260
- self._prevCommandIndex += 1
261
- self._prevCommandIndex = min(self._prevCommandIndex, 0)
262
-
263
- if self._prevCommands:
264
- self.setCommand()
265
-
266
- def setCommand(self):
267
- """Do the displaying of currently chosen command"""
268
- prevCommand = ''
269
- if self._prevCommandIndex:
270
- prevCommand = self._prevCommands[self._prevCommandIndex]
271
-
272
- cursor = self.textCursor()
273
- cursor.select(QTextCursor.LineUnderCursor)
274
- if cursor.selectedText().startswith(self._consolePrompt):
275
- prevCommand = "{}{}".format(self._consolePrompt, prevCommand)
276
- cursor.insertText(prevCommand)
277
- self.setTextCursor(cursor)
278
-
279
- def clear(self):
280
- """clears the text in the editor"""
281
- QTextEdit.clear(self)
282
- self.startInputLine()
283
-
284
- def clearToLastPrompt(self):
285
- # store the current cursor position so we can restore when we are done
286
- currentCursor = self.textCursor()
287
- # move to the end of the document so we can search backwards
288
- cursor = self.textCursor()
289
- cursor.movePosition(cursor.End)
290
- self.setTextCursor(cursor)
291
- # Check if the last line is a empty prompt. If so, then preform two finds so we
292
- # find the prompt we are looking for instead of this empty prompt
293
- findCount = (
294
- 2 if self.toPlainText()[-len(self.prompt()) :] == self.prompt() else 1
295
- )
296
- for _ in range(findCount):
297
- self.find(self.prompt(), QTextDocument.FindBackward)
298
- # move to the end of the found line, select the rest of the text and remove it
299
- # preserving history if there is anything to remove.
300
- cursor = self.textCursor()
301
- cursor.movePosition(cursor.EndOfLine)
302
- cursor.movePosition(cursor.End, cursor.KeepAnchor)
303
- txt = cursor.selectedText()
304
- if txt:
305
- self.setTextCursor(cursor)
306
- self.insertPlainText('')
307
- # Restore the cursor position to its original location
308
- self.setTextCursor(currentCursor)
309
-
310
- def commentColor(self):
311
- return self._commentColor
312
-
313
- def setCommentColor(self, color):
314
- self._commentColor = color
315
-
316
- def completer(self):
317
- """returns the completer instance that is associated with this editor"""
318
- return self._completer
319
-
320
- def errorMessageColor(self):
321
- return self.__class__._errorMessageColor
322
-
323
- def setErrorMessageColor(self, color):
324
- self.__class__._errorMessageColor = color
325
-
326
- def foregroundColor(self):
327
- return self._foregroundColor
328
-
329
- def setForegroundColor(self, color):
330
- self._foregroundColor = color
331
-
332
- def executeString(self, commandText, filename='<ConsoleEdit>', extraPrint=True):
333
- cursor = self.textCursor()
334
- cursor.select(QTextCursor.BlockUnderCursor)
335
- line = cursor.selectedText()
336
- if line and line[0] not in string.printable:
337
- line = line[1:]
338
-
339
- if line.startswith(self.prompt()) and extraPrint:
340
- print("")
341
-
342
- cmdresult = None
343
- # https://stackoverflow.com/a/29456463
344
- # If you want to get the result of the code, you have to call eval
345
- # however eval does not accept multiple statements. For that you need
346
- # exec which has no Return.
347
- wasEval = False
348
- startTime = time.time()
349
- try:
350
- compiled = compile(commandText, filename, 'eval')
351
- wasEval = True
352
- except Exception:
353
- compiled = compile(commandText, filename, 'exec')
354
- if wasEval:
355
- cmdresult = eval(compiled, __main__.__dict__, __main__.__dict__)
356
- else:
357
- exec(compiled, __main__.__dict__, __main__.__dict__)
358
- # Provide user feedback when running long code execution.
359
- delta = time.time() - startTime
360
- if self.flashTime and delta >= self.flashTime:
361
- blurdev.core.flashWindow()
362
- # Report the total time it took to execute this code.
363
- if self.reportExecutionTime is not None:
364
- self.reportExecutionTime(delta)
365
- return cmdresult, wasEval
366
-
367
- def executeCommand(self):
368
- """executes the current line of code"""
369
- # grab the command from the line
370
- block = self.textCursor().block().text()
371
- p = '{prompt}(.*)'.format(prompt=re.escape(self.prompt()))
372
- results = re.search(p, block)
373
- if results:
374
- commandText = results.groups()[0]
375
- # if the cursor position is at the end of the line
376
- if self.textCursor().atEnd():
377
- # insert a new line
378
- self.insertPlainText('\n')
379
-
380
- # update prevCommands list, but only if commandText is not the most
381
- # recent prevCommand, or there are no previous commands
382
- hasText = len(commandText) > 0
383
- prevCmds = self._prevCommands
384
- notPrevCmd = not prevCmds or prevCmds[-1] != commandText
385
- if hasText and notPrevCmd:
386
- self._prevCommands.append(commandText)
387
- # limit length of prevCommand list to max number of prev commands
388
- self._prevCommands = self._prevCommands[-1 * self._prevCommandsMax :]
389
-
390
- if self._pdbMode:
391
- if commandText:
392
- self.pdbSendCommand(commandText)
393
- else:
394
- # Sending a blank line to pdb will cause it to quit raising a
395
- # exception. Most likely the user just wants to add some white
396
- # space between their commands, so just add a new prompt line.
397
- self.startInputLine()
398
- self.insertPlainText(commandText)
399
- else:
400
- # evaluate the command
401
- cmdresult, wasEval = self.executeString(commandText)
402
-
403
- # print the resulting commands
404
- if cmdresult is not None:
405
- # When writing to additional stdout's not including a new line
406
- # makes the output not match the formatting you get inside the
407
- # console.
408
- self.write(u'{}\n'.format(cmdresult))
409
- # NOTE: I am using u'' above so unicode strings in python 2
410
- # don't get converted to str objects.
411
-
412
- self.startInputLine()
413
-
414
- # otherwise, move the command to the end of the line
415
- else:
416
- self.startInputLine()
417
- self.insertPlainText(commandText)
418
-
419
- # if no command, then start a new line
420
- else:
421
- self.startInputLine()
422
-
423
- def flush(self):
424
- pass
425
-
426
- def focusInEvent(self, event):
427
- """overload the focus in event to ensure the completer has the proper widget"""
428
- if self.completer():
429
- self.completer().setWidget(self)
430
- QTextEdit.focusInEvent(self, event)
431
-
432
- def insertCompletion(self, completion):
433
- """inserts the completion text into the editor"""
434
- if self.completer().widget() == self:
435
- cursor = self.textCursor()
436
- cursor.select(QTextCursor.WordUnderCursor)
437
- cursor.insertText(completion)
438
- self.setTextCursor(cursor)
439
-
440
- def insertFromMimeData(self, mimeData):
441
- html = False
442
- if mimeData.hasHtml():
443
- txt = mimeData.html()
444
- html = True
445
- else:
446
- txt = mimeData.text()
447
-
448
- doc = QTextDocument()
449
-
450
- if html:
451
- doc.setHtml(txt)
452
- else:
453
- doc.setPlainText(txt)
454
-
455
- txt = doc.toPlainText()
456
-
457
- exp = re.compile(
458
- (
459
- r'[^A-Za-z0-9\~\!\@\#\$\%\^\&\*\(\)\_\+\{\}\|\:'
460
- r'\"\<\>\?\`\-\=\[\]\\\;\'\,\.\/ \t\n]'
461
- )
462
- )
463
- newText = text(txt)
464
- for each in exp.findall(newText):
465
- newText = newText.replace(each, '?')
466
-
467
- self.insertPlainText(newText)
468
-
469
- def isatty(self):
470
- """Return True if the stream is interactive (i.e., connected to a terminal/tty
471
- device).
472
- """
473
- # This method is required for pytest to run in a DCC. Returns False so the
474
- # output does not contain cursor control characters that disrupt the visual
475
- # display of the output.
476
- return False
477
-
478
- def lastError(self):
479
- try:
480
- return ''.join(
481
- traceback.format_exception(
482
- sys.last_type, sys.last_value, sys.last_traceback
483
- )
484
- )
485
- except AttributeError:
486
- # last_traceback, last_type and last_value do not always exist
487
- return ''
488
-
489
- def keyPressEvent(self, event):
490
- """overload the key press event to handle custom events"""
491
-
492
- completer = self.completer()
493
-
494
- if completer and event.key() in (
495
- Qt.Key_Backspace,
496
- Qt.Key_Delete,
497
- Qt.Key_Escape,
498
- ):
499
- completer.hideDocumentation()
500
-
501
- # enter || return keys will execute the command
502
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
503
- if completer.popup().isVisible():
504
- completer.clear()
505
- event.ignore()
506
- else:
507
- self.executeCommand()
508
-
509
- # home key will move the cursor to home
510
- elif event.key() == Qt.Key_Home:
511
- self.moveToHome()
512
-
513
- # otherwise, ignore the event for completion events
514
- elif event.key() in (Qt.Key_Tab, Qt.Key_Backtab):
515
- if not completer.popup().isVisible():
516
- # The completer does not get updated if its not visible while typing.
517
- # We are about to complete the text using it so ensure its updated.
518
- completer.refreshList(scope=__main__.__dict__)
519
- completer.popup().setCurrentIndex(
520
- completer.completionModel().index(0, 0)
521
- )
522
- # Insert the correct text and clear the completion model
523
- index = completer.popup().currentIndex()
524
- self.insertCompletion(index.data(Qt.DisplayRole))
525
- completer.clear()
526
-
527
- elif event.key() == Qt.Key_Escape and completer.popup().isVisible():
528
- completer.clear()
529
-
530
- # other wise handle the keypress
531
- else:
532
- # define special key sequences
533
- modifiers = QApplication.instance().keyboardModifiers()
534
- ctrlSpace = event.key() == Qt.Key_Space and modifiers == Qt.ControlModifier
535
- ctrlM = event.key() == Qt.Key_M and modifiers == Qt.ControlModifier
536
- ctrlI = event.key() == Qt.Key_I and modifiers == Qt.ControlModifier
537
-
538
- # Process all events we do not want to override
539
- if not (ctrlSpace or ctrlM or ctrlI):
540
- QTextEdit.keyPressEvent(self, event)
541
-
542
- window = self.window()
543
- if ctrlI:
544
- hasToggleCase = hasattr(window, 'toggleCaseSensitive')
545
- if hasToggleCase:
546
- window.toggleCaseSensitive()
547
- if ctrlM:
548
- hasCycleMode = hasattr(window, 'cycleCompleterMode')
549
- if hasCycleMode:
550
- window.cycleCompleterMode()
551
-
552
- # check for particular events for the completion
553
- if completer:
554
- # look for documentation popups
555
- if event.key() == Qt.Key_ParenLeft:
556
- rect = self.cursorRect()
557
- point = self.mapToGlobal(QPoint(rect.x(), rect.y()))
558
- completer.showDocumentation(pos=point, scope=__main__.__dict__)
559
-
560
- # hide documentation popups
561
- elif event.key() == Qt.Key_ParenRight:
562
- completer.hideDocumentation()
563
-
564
- # determine if we need to show the popup or if it already is visible, we
565
- # need to update it
566
- elif (
567
- event.key() == Qt.Key_Period
568
- or event.key() == Qt.Key_Escape
569
- or completer.popup().isVisible()
570
- or ctrlSpace
571
- or ctrlI
572
- or ctrlM
573
- or completer.wasCompletingCounter
574
- ):
575
- completer.refreshList(scope=__main__.__dict__)
576
- completer.popup().setCurrentIndex(
577
- completer.completionModel().index(0, 0)
578
- )
579
-
580
- # show the completer for the rect
581
- rect = self.cursorRect()
582
- rect.setWidth(
583
- completer.popup().sizeHintForColumn(0)
584
- + completer.popup().verticalScrollBar().sizeHint().width()
585
- )
586
- completer.complete(rect)
587
-
588
- if completer.popup().isVisible():
589
- completer.wasCompleting = True
590
- completer.wasCompletingCounter = 0
591
-
592
- if completer.wasCompleting and not completer.popup().isVisible():
593
- wasCompletingCounterMax = completer.wasCompletingCounterMax
594
- if completer.wasCompletingCounter <= wasCompletingCounterMax:
595
- if event.key() not in (Qt.Key_Backspace, Qt.Key_Left):
596
- completer.wasCompletingCounter += 1
597
- else:
598
- completer.wasCompletingCounter = 0
599
- completer.wasCompleting = False
600
-
601
- def keywordColor(self):
602
- return self._keywordColor
603
-
604
- def setKeywordColor(self, color):
605
- self._keywordColor = color
606
-
607
- def moveToHome(self):
608
- """moves the cursor to the home location"""
609
- mode = QTextCursor.MoveAnchor
610
- # select the home
611
- if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
612
- mode = QTextCursor.KeepAnchor
613
- # grab the cursor
614
- cursor = self.textCursor()
615
- if QApplication.instance().keyboardModifiers() == Qt.ControlModifier:
616
- # move to the top of the document if control is pressed
617
- cursor.movePosition(QTextCursor.Start)
618
- else:
619
- # Otherwise just move it to the start of the line
620
- cursor.movePosition(QTextCursor.StartOfBlock, mode)
621
- # move the cursor to the end of the prompt.
622
- cursor.movePosition(QTextCursor.Right, mode, len(self.prompt()))
623
- self.setTextCursor(cursor)
624
-
625
- def outputPrompt(self):
626
- """The prompt used to output a result."""
627
- return self._outputPrompt
628
-
629
- def pdbContinue(self):
630
- self.pdbSendCommand('continue')
631
-
632
- def pdbDown(self):
633
- self.pdbSendCommand('down')
634
-
635
- def pdbNext(self):
636
- self.pdbSendCommand('next')
637
-
638
- def pdbStep(self):
639
- self.pdbSendCommand('step')
640
-
641
- def pdbUp(self):
642
- self.pdbSendCommand('up')
643
-
644
- def pdbMode(self):
645
- return self._pdbMode
646
-
647
- def setPdbMode(self, mode):
648
- if self.pdbModeAction:
649
- if not self.pdbModeAction.isEnabled():
650
- # pdbModeAction is disabled by default, enable the action, so the user
651
- # can switch between pdb and normal mode any time they want. pdbMode
652
- # does nothing if this instance of python is not the child process of
653
- # blurdev.external.External, and the parent process is in pdb mode.
654
- self.pdbModeAction.blockSignals(True)
655
- self.pdbModeAction.setChecked(mode)
656
- self.pdbModeAction.blockSignals(False)
657
- self.pdbModeAction.setEnabled(True)
658
- self._pdbMode = mode
659
- if self.pdbUpdateVisibility:
660
- self.pdbUpdateVisibility(mode)
661
- self.startInputLine()
662
-
663
- def pdbSendCommand(self, commandText):
664
- import blurdev.external
665
-
666
- blurdev.external.External(['pdb', '', {'msg': commandText}])
667
-
668
- def prompt(self):
669
- if self._pdbMode:
670
- return self._pdbPrompt
671
- return self._consolePrompt
672
-
673
- def resultColor(self):
674
- return self._resultColor
675
-
676
- def setResultColor(self, color):
677
- self._resultColor = color
678
-
679
- def setCompleter(self, completer):
680
- """sets the completer instance for this widget"""
681
- if completer:
682
- self._completer = completer
683
- completer.setWidget(self)
684
- completer.activated.connect(self.insertCompletion)
685
-
686
- def showEvent(self, event):
687
- # _firstShow is used to ensure the first imput prompt is styled by any active
688
- # stylesheet
689
- if self._firstShow:
690
- self.startInputLine()
691
- self._firstShow = False
692
- super(ConsoleEdit, self).showEvent(event)
693
-
694
- def startInputLine(self):
695
- """create a new command prompt line"""
696
- self.startPrompt(self.prompt())
697
-
698
- def startPrompt(self, prompt):
699
- """create a new command prompt line with the given prompt
700
-
701
- Args:
702
- prompt(str): The prompt to start the line with. If this prompt
703
- is already the only text on the last line this function does nothing.
704
- """
705
- self.moveCursor(QTextCursor.End)
706
-
707
- # if this is not already a new line
708
- if self.textCursor().block().text() != prompt:
709
- charFormat = QTextCharFormat()
710
- self.setCurrentCharFormat(charFormat)
711
-
712
- inputstr = prompt
713
- if self.textCursor().block().text():
714
- inputstr = '\n' + inputstr
715
-
716
- self.insertPlainText(inputstr)
717
-
718
- def startOutputLine(self):
719
- """Create a new line to show output text."""
720
- self.startPrompt(self._outputPrompt)
721
-
722
- def stdoutColor(self):
723
- return self._stdoutColor
724
-
725
- def setStdoutColor(self, color):
726
- self._stdoutColor = color
727
-
728
- def stringColor(self):
729
- return self._stringColor
730
-
731
- def setStringColor(self, color):
732
- self._stringColor = color
733
-
734
- def removeCurrentLine(self):
735
- self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
736
- self.moveCursor(QTextCursor.StartOfLine, QTextCursor.MoveAnchor)
737
- self.moveCursor(QTextCursor.End, QTextCursor.KeepAnchor)
738
- self.textCursor().removeSelectedText()
739
- self.textCursor().deletePreviousChar()
740
- self.insertPlainText("\n")
741
-
742
- def parseErrorHyperLinkInfo(self, txt):
743
- """Determine if txt is a File-info line from a traceback, and if so, return info
744
- dict.
745
- """
746
- lineMarker = '", line '
747
- ret = None
748
-
749
- filenameEnd = txt.find(lineMarker)
750
- if txt[:8] == ' File "' and filenameEnd >= 0:
751
- filename = txt[8:filenameEnd]
752
- lineNumStart = filenameEnd + len(lineMarker)
753
- lineNumEnd = txt.find(',', lineNumStart)
754
- if lineNumEnd == -1:
755
- lineNumEnd = len(txt)
756
- lineNum = txt[lineNumStart:lineNumEnd]
757
- ret = {
758
- 'filename': filename,
759
- 'fileStart': 8,
760
- 'fileEnd': filenameEnd,
761
- 'lineNum': lineNum,
762
- }
763
-
764
- return ret
765
-
766
- def write(self, msg, error=False):
767
- """write the message to the logger"""
768
- # Convert the stream_manager's stream to the boolean value this function expects
769
- error = error == stream.STDERR
770
- # Check that we haven't been garbage collected before trying to write.
771
- # This can happen while shutting down a QApplication like Nuke.
772
- if QtCompat.isValid(self):
773
- window = self.window()
774
- doHyperlink = (
775
- hasattr(window, 'uiErrorHyperlinksACT')
776
- and window.uiErrorHyperlinksACT.isChecked()
777
- )
778
- self.moveCursor(QTextCursor.End)
779
-
780
- charFormat = QTextCharFormat()
781
- if not error:
782
- charFormat.setForeground(self.stdoutColor())
783
- else:
784
- charFormat.setForeground(self.errorMessageColor())
785
- self.setCurrentCharFormat(charFormat)
786
-
787
- # If showing Error Hyperlinks... Sometimes (when a syntax error, at least),
788
- # the last File-Info line of a traceback is issued in multiple messages
789
- # starting with unicode paragraph separator (r"\u2029") and followed by a
790
- # newline, so our normal string checks search won't work. Instead, we'll
791
- # manually reconstruct the line. If msg is a newline, grab that current line
792
- # and check it. If it matches,proceed using that line as msg
793
- cursor = self.textCursor()
794
- info = None
795
-
796
- if doHyperlink and msg == '\n':
797
- cursor.select(QTextCursor.BlockUnderCursor)
798
- line = cursor.selectedText()
799
-
800
- # Remove possible leading unicode paragraph separator, which really
801
- # messes up the works
802
- if line and line[0] not in string.printable:
803
- line = line[1:]
804
-
805
- info = self.parseErrorHyperLinkInfo(line)
806
- if info:
807
- cursor.insertText("\n")
808
- msg = "{}\n".format(line)
809
-
810
- # If showing Error Hyperlinks, display underline output, otherwise
811
- # display normal output. Exclude ConsoleEdits
812
- info = info if info else self.parseErrorHyperLinkInfo(msg)
813
- filename = info.get("filename", "") if info else ""
814
- isConsoleEdit = '<ConsoleEdit>' in filename
815
-
816
- if info and doHyperlink and not isConsoleEdit:
817
- fileStart = info.get("fileStart")
818
- fileEnd = info.get("fileEnd")
819
- lineNum = info.get("lineNum")
820
-
821
- isWorkbox = (
822
- '<WorkboxSelection>' in filename
823
- or '<WorkboxWidget>' in filename
824
- )
825
- if isWorkbox:
826
- split = filename.split(':')
827
- workboxIdx = split[-1]
828
- filename = ''
829
- else:
830
- filename = filename
831
- workboxIdx = ''
832
- href = '{}, {}, {}'.format(filename, workboxIdx, lineNum)
833
-
834
- # Insert initial, non-underlined text
835
- cursor.insertText(msg[:fileStart])
836
-
837
- # Insert hyperlink
838
- fmt = cursor.charFormat()
839
- fmt.setAnchor(True)
840
- fmt.setAnchorHref(href)
841
- fmt.setFontUnderline(True)
842
- toolTip = "Open {} at line number {}".format(filename, lineNum)
843
- fmt.setToolTip(toolTip)
844
- cursor.insertText(msg[fileStart:fileEnd], fmt)
845
-
846
- # Insert the rest of the msg
847
- fmt.setAnchor(False)
848
- fmt.setAnchorHref('')
849
- fmt.setFontUnderline(False)
850
- fmt.setToolTip('')
851
- cursor.insertText(msg[fileEnd:], fmt)
852
- else:
853
- # Non-hyperlink output
854
- self.insertPlainText(msg)
855
-
856
- # if a outputPipe was provided, write the message to that pipe
857
- if self.outputPipe:
858
- self.outputPipe(msg, error=error)
1
+ """ LoggerWindow class is an overloaded python interpreter for preditor"""
2
+ from __future__ import absolute_import, print_function
3
+
4
+ import os
5
+ import re
6
+ import string
7
+ import subprocess
8
+ import sys
9
+ import time
10
+ import traceback
11
+ from builtins import str as text
12
+
13
+ import __main__
14
+ from Qt import QtCompat
15
+ from Qt.QtCore import QPoint, Qt
16
+ from Qt.QtGui import QColor, QFontMetrics, QTextCharFormat, QTextCursor, QTextDocument
17
+ from Qt.QtWidgets import QAction, QApplication, QTextEdit
18
+
19
+ from .. import debug, settings, stream
20
+ from ..streamhandler_helper import StreamHandlerHelper
21
+ from .codehighlighter import CodeHighlighter
22
+ from .completer import PythonCompleter
23
+
24
+
25
+ class ConsolePrEdit(QTextEdit):
26
+ # Ensure the error prompt only shows up once.
27
+ _errorPrompted = False
28
+ # the color error messages are displayed in, can be set by stylesheets
29
+ _errorMessageColor = QColor(Qt.red)
30
+
31
+ def __init__(self, parent):
32
+ super(ConsolePrEdit, self).__init__(parent)
33
+ # store the error buffer
34
+ self._completer = None
35
+
36
+ # If populated, also write to this interface
37
+ self.outputPipe = None
38
+
39
+ self._stdoutColor = QColor(17, 154, 255)
40
+ self._commentColor = QColor(0, 206, 52)
41
+ self._keywordColor = QColor(17, 154, 255)
42
+ self._stringColor = QColor(255, 128, 0)
43
+ self._resultColor = QColor(128, 128, 128)
44
+
45
+ self._consolePrompt = '>>> '
46
+ # Note: Changing _outputPrompt may require updating resource\lang\python.xml
47
+ # If still using a #
48
+ self._outputPrompt = '#Result: '
49
+ # Method used to update the gui when code is executed
50
+ self.reportExecutionTime = None
51
+
52
+ self._firstShow = True
53
+
54
+ # When executing code, that takes longer than this seconds, flash the window
55
+ self.flash_time = 1.0
56
+ self.flash_window = None
57
+
58
+ # Store previous commands to retrieve easily
59
+ self._prevCommands = []
60
+ self._prevCommandIndex = 0
61
+ self._prevCommandsMax = 100
62
+
63
+ # create the completer
64
+ self.setCompleter(PythonCompleter(self))
65
+
66
+ # sys.__stdout__ doesn't work if some third party has implemented their own
67
+ # override. Use these to backup the current logger so the logger displays
68
+ # output, but application specific consoles also get the info.
69
+ self.stdout = None
70
+ self.stderr = None
71
+ self._errorLog = None
72
+
73
+ # overload the sys logger
74
+ self.stream_manager = stream.install_to_std()
75
+ # Redirect future writes directly to the console, add any previous writes
76
+ # to the console and free up the memory consumed by previous writes as we
77
+ # assume this is likely to be the only callback added to the manager.
78
+ self.stream_manager.add_callback(
79
+ self.write, replay=True, disable_writes=True, clear=True
80
+ )
81
+ # Store the current outputs
82
+ self.stdout = sys.stdout
83
+ self.stderr = sys.stderr
84
+ self._errorLog = sys.stderr
85
+ debug.BlurExcepthook.install()
86
+
87
+ # Update any StreamHandler's that were setup using the old stdout/err
88
+ StreamHandlerHelper.replace_stream(self.stdout, sys.stdout)
89
+ StreamHandlerHelper.replace_stream(self.stderr, sys.stderr)
90
+
91
+ # create the highlighter
92
+ highlight = CodeHighlighter(self)
93
+ highlight.setLanguage('Python')
94
+ self.uiCodeHighlighter = highlight
95
+
96
+ self.uiClearToLastPromptACT = QAction('Clear to Last', self)
97
+ self.uiClearToLastPromptACT.triggered.connect(self.clearToLastPrompt)
98
+ self.uiClearToLastPromptACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_Backspace)
99
+ self.addAction(self.uiClearToLastPromptACT)
100
+
101
+ self.x = 0
102
+ self.clickPos = None
103
+ self.anchor = None
104
+
105
+ def setConsoleFont(self, font):
106
+ """Set the console's font and adjust the tabStopWidth"""
107
+ self.setFont(font)
108
+
109
+ # Set the setTabStopWidth for the console's font
110
+ tab_width = 4
111
+ # TODO: Make tab_width a general user setting
112
+ if hasattr(self, "window") and "LoggerWindow" in str(type(self.window())):
113
+ # If parented to a LoggerWindow, get the tab_width from it's workboxes
114
+ workbox = self.window().current_workbox()
115
+ if workbox:
116
+ tab_width = workbox.__tab_width__()
117
+ fontPixelWidth = QFontMetrics(font).width(" ")
118
+ self.setTabStopWidth(fontPixelWidth * tab_width)
119
+
120
+ def mousePressEvent(self, event):
121
+ """Overload of mousePressEvent to capture click position, so on release, we can
122
+ check release position. If it's the same (ie user clicked vs click-drag to
123
+ select text), we check if user clicked an error hyperlink.
124
+ """
125
+ self.clickPos = event.pos()
126
+ self.anchor = self.anchorAt(event.pos())
127
+ if self.anchor:
128
+ QApplication.setOverrideCursor(Qt.PointingHandCursor)
129
+ return super(ConsolePrEdit, self).mousePressEvent(event)
130
+
131
+ def mouseReleaseEvent(self, event):
132
+ """Overload of mouseReleaseEvent to capture if user has left clicked... Check if
133
+ click position is the same as release position, if so, call errorHyperlink.
134
+ """
135
+ samePos = event.pos() == self.clickPos
136
+ left = event.button() == Qt.LeftButton
137
+ if samePos and left and self.anchor:
138
+ self.errorHyperlink()
139
+
140
+ self.clickPos = None
141
+ self.anchor = None
142
+ QApplication.restoreOverrideCursor()
143
+ return super(ConsolePrEdit, self).mouseReleaseEvent(event)
144
+
145
+ def wheelEvent(self, event):
146
+ """Override of wheelEvent to allow for font resizing by holding ctrl while"""
147
+ # scrolling. If used in LoggerWindow, use that wheel event
148
+ # May not want to import LoggerWindow, so perhaps
149
+ # check by str(type())
150
+ ctrlPressed = event.modifiers() == Qt.ControlModifier
151
+ if ctrlPressed and "LoggerWindow" in str(type(self.window())):
152
+ self.window().wheelEvent(event)
153
+ else:
154
+ QTextEdit.wheelEvent(self, event)
155
+
156
+ def keyReleaseEvent(self, event):
157
+ """Override of keyReleaseEvent to determine when to end navigation of
158
+ previous commands
159
+ """
160
+ if event.key() == Qt.Key_Alt:
161
+ self._prevCommandIndex = 0
162
+ else:
163
+ event.ignore()
164
+
165
+ def errorHyperlink(self):
166
+ """Determine if chosen line is an error traceback file-info line, if so, parse
167
+ the filepath and line number, and attempt to open the module file in the user's
168
+ chosen text editor at the relevant line, using specified Command Prompt pattern.
169
+
170
+ The text editor defaults to SublimeText3, in the normal install directory
171
+ """
172
+ window = self.window()
173
+
174
+ # Bail if Error Hyperlinks setting is not turned on or we don't have an anchor.
175
+ doHyperlink = (
176
+ hasattr(window, 'uiErrorHyperlinksACT')
177
+ and window.uiErrorHyperlinksACT.isChecked()
178
+ and self.anchor
179
+ )
180
+ if not doHyperlink:
181
+ return
182
+
183
+ # info is a comma separated string, in the form: "filename, workboxIdx, lineNum"
184
+ info = self.anchor.split(', ')
185
+ modulePath = info[0]
186
+ workboxIndex = info[1]
187
+ lineNum = info[2]
188
+
189
+ # fetch info from LoggerWindow
190
+ exePath = ''
191
+ cmdTempl = ''
192
+ if hasattr(window, 'textEditorPath'):
193
+ exePath = window.textEditorPath
194
+ cmdTempl = window.textEditorCmdTempl
195
+
196
+ # Bail if not setup properly
197
+ msg = "Cannot use traceback hyperlink. "
198
+ if not exePath:
199
+ msg += "No text editor path defined."
200
+ print(msg)
201
+ return
202
+ if not os.path.exists(exePath):
203
+ msg += "Text editor executable does not exist: {}".format(exePath)
204
+ print(msg)
205
+ return
206
+ if not cmdTempl:
207
+ msg += "No text editor Command Prompt command template defined."
208
+ print(msg)
209
+ return
210
+ if modulePath and not os.path.exists(modulePath):
211
+ msg += "Specified module path does not exist: {}".format(modulePath)
212
+ print(msg)
213
+ return
214
+
215
+ if modulePath:
216
+ # Attempt to create command from template and run the command
217
+ try:
218
+ command = cmdTempl.format(
219
+ exePath=exePath, modulePath=modulePath, lineNum=lineNum
220
+ )
221
+ subprocess.Popen(command)
222
+ except (ValueError, OSError):
223
+ msg = "The provided text editor command template is not valid:\n {}"
224
+ msg = msg.format(cmdTempl)
225
+ print(msg)
226
+ elif workboxIndex is not None:
227
+ group, editor = workboxIndex.split(',')
228
+ lineNum = int(lineNum)
229
+ workbox = window.uiWorkboxTAB.set_current_groups_from_index(
230
+ int(group), int(editor)
231
+ )
232
+ workbox.__goto_line__(lineNum)
233
+ workbox.setFocus()
234
+
235
+ def getPrevCommand(self):
236
+ """Find and display the previous command in stack"""
237
+ self._prevCommandIndex -= 1
238
+
239
+ if abs(self._prevCommandIndex) > len(self._prevCommands):
240
+ self._prevCommandIndex += 1
241
+
242
+ if self._prevCommands:
243
+ self.setCommand()
244
+
245
+ def getNextCommand(self):
246
+ """Find and display the next command in stack"""
247
+ self._prevCommandIndex += 1
248
+ self._prevCommandIndex = min(self._prevCommandIndex, 0)
249
+
250
+ if self._prevCommands:
251
+ self.setCommand()
252
+
253
+ def setCommand(self):
254
+ """Do the displaying of currently chosen command"""
255
+ prevCommand = ''
256
+ if self._prevCommandIndex:
257
+ prevCommand = self._prevCommands[self._prevCommandIndex]
258
+
259
+ cursor = self.textCursor()
260
+ cursor.select(QTextCursor.LineUnderCursor)
261
+ if cursor.selectedText().startswith(self._consolePrompt):
262
+ prevCommand = "{}{}".format(self._consolePrompt, prevCommand)
263
+ cursor.insertText(prevCommand)
264
+ self.setTextCursor(cursor)
265
+
266
+ def clear(self):
267
+ """clears the text in the editor"""
268
+ QTextEdit.clear(self)
269
+ self.startInputLine()
270
+
271
+ def clearToLastPrompt(self):
272
+ # store the current cursor position so we can restore when we are done
273
+ currentCursor = self.textCursor()
274
+ # move to the end of the document so we can search backwards
275
+ cursor = self.textCursor()
276
+ cursor.movePosition(cursor.End)
277
+ self.setTextCursor(cursor)
278
+ # Check if the last line is a empty prompt. If so, then preform two finds so we
279
+ # find the prompt we are looking for instead of this empty prompt
280
+ findCount = (
281
+ 2 if self.toPlainText()[-len(self.prompt()) :] == self.prompt() else 1
282
+ )
283
+ for _ in range(findCount):
284
+ self.find(self.prompt(), QTextDocument.FindBackward)
285
+ # move to the end of the found line, select the rest of the text and remove it
286
+ # preserving history if there is anything to remove.
287
+ cursor = self.textCursor()
288
+ cursor.movePosition(cursor.EndOfLine)
289
+ cursor.movePosition(cursor.End, cursor.KeepAnchor)
290
+ txt = cursor.selectedText()
291
+ if txt:
292
+ self.setTextCursor(cursor)
293
+ self.insertPlainText('')
294
+ # Restore the cursor position to its original location
295
+ self.setTextCursor(currentCursor)
296
+
297
+ def commentColor(self):
298
+ return self._commentColor
299
+
300
+ def setCommentColor(self, color):
301
+ self._commentColor = color
302
+
303
+ def completer(self):
304
+ """returns the completer instance that is associated with this editor"""
305
+ return self._completer
306
+
307
+ def errorMessageColor(self):
308
+ return self.__class__._errorMessageColor
309
+
310
+ def setErrorMessageColor(self, color):
311
+ self.__class__._errorMessageColor = color
312
+
313
+ def foregroundColor(self):
314
+ return self._foregroundColor
315
+
316
+ def setForegroundColor(self, color):
317
+ self._foregroundColor = color
318
+
319
+ def executeString(self, commandText, filename='<ConsolePrEdit>', extraPrint=True):
320
+ cursor = self.textCursor()
321
+ cursor.select(QTextCursor.BlockUnderCursor)
322
+ line = cursor.selectedText()
323
+ if line and line[0] not in string.printable:
324
+ line = line[1:]
325
+
326
+ if line.startswith(self.prompt()) and extraPrint:
327
+ print("")
328
+
329
+ cmdresult = None
330
+ # https://stackoverflow.com/a/29456463
331
+ # If you want to get the result of the code, you have to call eval
332
+ # however eval does not accept multiple statements. For that you need
333
+ # exec which has no Return.
334
+ wasEval = False
335
+ startTime = time.time()
336
+ try:
337
+ compiled = compile(commandText, filename, 'eval')
338
+ wasEval = True
339
+ except Exception:
340
+ compiled = compile(commandText, filename, 'exec')
341
+ if wasEval:
342
+ cmdresult = eval(compiled, __main__.__dict__, __main__.__dict__)
343
+ else:
344
+ exec(compiled, __main__.__dict__, __main__.__dict__)
345
+
346
+ # Provide user feedback when running long code execution.
347
+ delta = time.time() - startTime
348
+ if self.flash_window and self.flash_time and delta >= self.flash_time:
349
+ if settings.OS_TYPE == "Windows":
350
+ try:
351
+ from casement import utils
352
+ except ImportError:
353
+ # If casement is not installed, flash window is disabled
354
+ pass
355
+ else:
356
+ hwnd = int(self.flash_window.winId())
357
+ utils.flash_window(hwnd)
358
+
359
+ # Report the total time it took to execute this code.
360
+ if self.reportExecutionTime is not None:
361
+ self.reportExecutionTime(delta)
362
+ return cmdresult, wasEval
363
+
364
+ def executeCommand(self):
365
+ """executes the current line of code"""
366
+ # grab the command from the line
367
+ block = self.textCursor().block().text()
368
+ p = '{prompt}(.*)'.format(prompt=re.escape(self.prompt()))
369
+ results = re.search(p, block)
370
+ if results:
371
+ commandText = results.groups()[0]
372
+ # if the cursor position is at the end of the line
373
+ if self.textCursor().atEnd():
374
+ # insert a new line
375
+ self.insertPlainText('\n')
376
+
377
+ # update prevCommands list, but only if commandText is not the most
378
+ # recent prevCommand, or there are no previous commands
379
+ hasText = len(commandText) > 0
380
+ prevCmds = self._prevCommands
381
+ notPrevCmd = not prevCmds or prevCmds[-1] != commandText
382
+ if hasText and notPrevCmd:
383
+ self._prevCommands.append(commandText)
384
+ # limit length of prevCommand list to max number of prev commands
385
+ self._prevCommands = self._prevCommands[-1 * self._prevCommandsMax :]
386
+
387
+ # evaluate the command
388
+ cmdresult, wasEval = self.executeString(commandText)
389
+
390
+ # print the resulting commands
391
+ if cmdresult is not None:
392
+ # When writing to additional stdout's not including a new line
393
+ # makes the output not match the formatting you get inside the
394
+ # console.
395
+ self.write(u'{}\n'.format(cmdresult))
396
+ # NOTE: I am using u'' above so unicode strings in python 2
397
+ # don't get converted to str objects.
398
+
399
+ self.startInputLine()
400
+
401
+ # otherwise, move the command to the end of the line
402
+ else:
403
+ self.startInputLine()
404
+ self.insertPlainText(commandText)
405
+
406
+ # if no command, then start a new line
407
+ else:
408
+ self.startInputLine()
409
+
410
+ def flush(self):
411
+ pass
412
+
413
+ def focusInEvent(self, event):
414
+ """overload the focus in event to ensure the completer has the proper widget"""
415
+ if self.completer():
416
+ self.completer().setWidget(self)
417
+ QTextEdit.focusInEvent(self, event)
418
+
419
+ def insertCompletion(self, completion):
420
+ """inserts the completion text into the editor"""
421
+ if self.completer().widget() == self:
422
+ cursor = self.textCursor()
423
+ cursor.select(QTextCursor.WordUnderCursor)
424
+ cursor.insertText(completion)
425
+ self.setTextCursor(cursor)
426
+
427
+ def insertFromMimeData(self, mimeData):
428
+ html = False
429
+ if mimeData.hasHtml():
430
+ txt = mimeData.html()
431
+ html = True
432
+ else:
433
+ txt = mimeData.text()
434
+
435
+ doc = QTextDocument()
436
+
437
+ if html:
438
+ doc.setHtml(txt)
439
+ else:
440
+ doc.setPlainText(txt)
441
+
442
+ txt = doc.toPlainText()
443
+
444
+ exp = re.compile(
445
+ (
446
+ r'[^A-Za-z0-9\~\!\@\#\$\%\^\&\*\(\)\_\+\{\}\|\:'
447
+ r'\"\<\>\?\`\-\=\[\]\\\;\'\,\.\/ \t\n]'
448
+ )
449
+ )
450
+ newText = text(txt)
451
+ for each in exp.findall(newText):
452
+ newText = newText.replace(each, '?')
453
+
454
+ self.insertPlainText(newText)
455
+
456
+ def isatty(self):
457
+ """Return True if the stream is interactive (i.e., connected to a terminal/tty
458
+ device).
459
+ """
460
+ # This method is required for pytest to run in a DCC. Returns False so the
461
+ # output does not contain cursor control characters that disrupt the visual
462
+ # display of the output.
463
+ return False
464
+
465
+ def lastError(self):
466
+ try:
467
+ return ''.join(
468
+ traceback.format_exception(
469
+ sys.last_type, sys.last_value, sys.last_traceback
470
+ )
471
+ )
472
+ except AttributeError:
473
+ # last_traceback, last_type and last_value do not always exist
474
+ return ''
475
+
476
+ def keyPressEvent(self, event):
477
+ """overload the key press event to handle custom events"""
478
+
479
+ completer = self.completer()
480
+
481
+ if completer and event.key() in (
482
+ Qt.Key_Backspace,
483
+ Qt.Key_Delete,
484
+ Qt.Key_Escape,
485
+ ):
486
+ completer.hideDocumentation()
487
+
488
+ # enter || return keys will execute the command
489
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
490
+ if completer.popup().isVisible():
491
+ completer.clear()
492
+ event.ignore()
493
+ else:
494
+ self.executeCommand()
495
+
496
+ # home key will move the cursor to home
497
+ elif event.key() == Qt.Key_Home:
498
+ self.moveToHome()
499
+
500
+ # otherwise, ignore the event for completion events
501
+ elif event.key() in (Qt.Key_Tab, Qt.Key_Backtab):
502
+ if not completer.popup().isVisible():
503
+ # The completer does not get updated if its not visible while typing.
504
+ # We are about to complete the text using it so ensure its updated.
505
+ completer.refreshList(scope=__main__.__dict__)
506
+ completer.popup().setCurrentIndex(
507
+ completer.completionModel().index(0, 0)
508
+ )
509
+ # Insert the correct text and clear the completion model
510
+ index = completer.popup().currentIndex()
511
+ self.insertCompletion(index.data(Qt.DisplayRole))
512
+ completer.clear()
513
+
514
+ elif event.key() == Qt.Key_Escape and completer.popup().isVisible():
515
+ completer.clear()
516
+
517
+ # other wise handle the keypress
518
+ else:
519
+ # define special key sequences
520
+ modifiers = QApplication.instance().keyboardModifiers()
521
+ ctrlSpace = event.key() == Qt.Key_Space and modifiers == Qt.ControlModifier
522
+ ctrlM = event.key() == Qt.Key_M and modifiers == Qt.ControlModifier
523
+ ctrlI = event.key() == Qt.Key_I and modifiers == Qt.ControlModifier
524
+
525
+ # Process all events we do not want to override
526
+ if not (ctrlSpace or ctrlM or ctrlI):
527
+ QTextEdit.keyPressEvent(self, event)
528
+
529
+ window = self.window()
530
+ if ctrlI:
531
+ hasToggleCase = hasattr(window, 'toggleCaseSensitive')
532
+ if hasToggleCase:
533
+ window.toggleCaseSensitive()
534
+ if ctrlM:
535
+ hasCycleMode = hasattr(window, 'cycleCompleterMode')
536
+ if hasCycleMode:
537
+ window.cycleCompleterMode()
538
+
539
+ # check for particular events for the completion
540
+ if completer:
541
+ # look for documentation popups
542
+ if event.key() == Qt.Key_ParenLeft:
543
+ rect = self.cursorRect()
544
+ point = self.mapToGlobal(QPoint(rect.x(), rect.y()))
545
+ completer.showDocumentation(pos=point, scope=__main__.__dict__)
546
+
547
+ # hide documentation popups
548
+ elif event.key() == Qt.Key_ParenRight:
549
+ completer.hideDocumentation()
550
+
551
+ # determine if we need to show the popup or if it already is visible, we
552
+ # need to update it
553
+ elif (
554
+ event.key() == Qt.Key_Period
555
+ or event.key() == Qt.Key_Escape
556
+ or completer.popup().isVisible()
557
+ or ctrlSpace
558
+ or ctrlI
559
+ or ctrlM
560
+ or completer.wasCompletingCounter
561
+ ):
562
+ completer.refreshList(scope=__main__.__dict__)
563
+ completer.popup().setCurrentIndex(
564
+ completer.completionModel().index(0, 0)
565
+ )
566
+
567
+ # show the completer for the rect
568
+ rect = self.cursorRect()
569
+ rect.setWidth(
570
+ completer.popup().sizeHintForColumn(0)
571
+ + completer.popup().verticalScrollBar().sizeHint().width()
572
+ )
573
+ completer.complete(rect)
574
+
575
+ if completer.popup().isVisible():
576
+ completer.wasCompleting = True
577
+ completer.wasCompletingCounter = 0
578
+
579
+ if completer.wasCompleting and not completer.popup().isVisible():
580
+ wasCompletingCounterMax = completer.wasCompletingCounterMax
581
+ if completer.wasCompletingCounter <= wasCompletingCounterMax:
582
+ if event.key() not in (Qt.Key_Backspace, Qt.Key_Left):
583
+ completer.wasCompletingCounter += 1
584
+ else:
585
+ completer.wasCompletingCounter = 0
586
+ completer.wasCompleting = False
587
+
588
+ def keywordColor(self):
589
+ return self._keywordColor
590
+
591
+ def setKeywordColor(self, color):
592
+ self._keywordColor = color
593
+
594
+ def moveToHome(self):
595
+ """moves the cursor to the home location"""
596
+ mode = QTextCursor.MoveAnchor
597
+ # select the home
598
+ if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
599
+ mode = QTextCursor.KeepAnchor
600
+ # grab the cursor
601
+ cursor = self.textCursor()
602
+ if QApplication.instance().keyboardModifiers() == Qt.ControlModifier:
603
+ # move to the top of the document if control is pressed
604
+ cursor.movePosition(QTextCursor.Start)
605
+ else:
606
+ # Otherwise just move it to the start of the line
607
+ cursor.movePosition(QTextCursor.StartOfBlock, mode)
608
+ # move the cursor to the end of the prompt.
609
+ cursor.movePosition(QTextCursor.Right, mode, len(self.prompt()))
610
+ self.setTextCursor(cursor)
611
+
612
+ def outputPrompt(self):
613
+ """The prompt used to output a result."""
614
+ return self._outputPrompt
615
+
616
+ def prompt(self):
617
+ return self._consolePrompt
618
+
619
+ def resultColor(self):
620
+ return self._resultColor
621
+
622
+ def setResultColor(self, color):
623
+ self._resultColor = color
624
+
625
+ def setCompleter(self, completer):
626
+ """sets the completer instance for this widget"""
627
+ if completer:
628
+ self._completer = completer
629
+ completer.setWidget(self)
630
+ completer.activated.connect(self.insertCompletion)
631
+
632
+ def showEvent(self, event):
633
+ # _firstShow is used to ensure the first imput prompt is styled by any active
634
+ # stylesheet
635
+ if self._firstShow:
636
+ self.startInputLine()
637
+ self._firstShow = False
638
+ super(ConsolePrEdit, self).showEvent(event)
639
+
640
+ def startInputLine(self):
641
+ """create a new command prompt line"""
642
+ self.startPrompt(self.prompt())
643
+
644
+ def startPrompt(self, prompt):
645
+ """create a new command prompt line with the given prompt
646
+
647
+ Args:
648
+ prompt(str): The prompt to start the line with. If this prompt
649
+ is already the only text on the last line this function does nothing.
650
+ """
651
+ self.moveCursor(QTextCursor.End)
652
+
653
+ # if this is not already a new line
654
+ if self.textCursor().block().text() != prompt:
655
+ charFormat = QTextCharFormat()
656
+ self.setCurrentCharFormat(charFormat)
657
+
658
+ inputstr = prompt
659
+ if self.textCursor().block().text():
660
+ inputstr = '\n' + inputstr
661
+
662
+ self.insertPlainText(inputstr)
663
+
664
+ def startOutputLine(self):
665
+ """Create a new line to show output text."""
666
+ self.startPrompt(self._outputPrompt)
667
+
668
+ def stdoutColor(self):
669
+ return self._stdoutColor
670
+
671
+ def setStdoutColor(self, color):
672
+ self._stdoutColor = color
673
+
674
+ def stringColor(self):
675
+ return self._stringColor
676
+
677
+ def setStringColor(self, color):
678
+ self._stringColor = color
679
+
680
+ def removeCurrentLine(self):
681
+ self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
682
+ self.moveCursor(QTextCursor.StartOfLine, QTextCursor.MoveAnchor)
683
+ self.moveCursor(QTextCursor.End, QTextCursor.KeepAnchor)
684
+ self.textCursor().removeSelectedText()
685
+ self.textCursor().deletePreviousChar()
686
+ self.insertPlainText("\n")
687
+
688
+ def parseErrorHyperLinkInfo(self, txt):
689
+ """Determine if txt is a File-info line from a traceback, and if so, return info
690
+ dict.
691
+ """
692
+ lineMarker = '", line '
693
+ ret = None
694
+
695
+ filenameEnd = txt.find(lineMarker)
696
+ if txt[:8] == ' File "' and filenameEnd >= 0:
697
+ filename = txt[8:filenameEnd]
698
+ lineNumStart = filenameEnd + len(lineMarker)
699
+ lineNumEnd = txt.find(',', lineNumStart)
700
+ if lineNumEnd == -1:
701
+ lineNumEnd = len(txt)
702
+ lineNum = txt[lineNumStart:lineNumEnd]
703
+ ret = {
704
+ 'filename': filename,
705
+ 'fileStart': 8,
706
+ 'fileEnd': filenameEnd,
707
+ 'lineNum': lineNum,
708
+ }
709
+
710
+ return ret
711
+
712
+ def write(self, msg, error=False):
713
+ """write the message to the logger"""
714
+ # Convert the stream_manager's stream to the boolean value this function expects
715
+ error = error == stream.STDERR
716
+ # Check that we haven't been garbage collected before trying to write.
717
+ # This can happen while shutting down a QApplication like Nuke.
718
+ if QtCompat.isValid(self):
719
+ window = self.window()
720
+ doHyperlink = (
721
+ hasattr(window, 'uiErrorHyperlinksACT')
722
+ and window.uiErrorHyperlinksACT.isChecked()
723
+ )
724
+ self.moveCursor(QTextCursor.End)
725
+
726
+ charFormat = QTextCharFormat()
727
+ if not error:
728
+ charFormat.setForeground(self.stdoutColor())
729
+ else:
730
+ charFormat.setForeground(self.errorMessageColor())
731
+ self.setCurrentCharFormat(charFormat)
732
+
733
+ # If showing Error Hyperlinks... Sometimes (when a syntax error, at least),
734
+ # the last File-Info line of a traceback is issued in multiple messages
735
+ # starting with unicode paragraph separator (r"\u2029") and followed by a
736
+ # newline, so our normal string checks search won't work. Instead, we'll
737
+ # manually reconstruct the line. If msg is a newline, grab that current line
738
+ # and check it. If it matches,proceed using that line as msg
739
+ cursor = self.textCursor()
740
+ info = None
741
+
742
+ if doHyperlink and msg == '\n':
743
+ cursor.select(QTextCursor.BlockUnderCursor)
744
+ line = cursor.selectedText()
745
+
746
+ # Remove possible leading unicode paragraph separator, which really
747
+ # messes up the works
748
+ if line and line[0] not in string.printable:
749
+ line = line[1:]
750
+
751
+ info = self.parseErrorHyperLinkInfo(line)
752
+ if info:
753
+ cursor.insertText("\n")
754
+ msg = "{}\n".format(line)
755
+
756
+ # If showing Error Hyperlinks, display underline output, otherwise
757
+ # display normal output. Exclude ConsolePrEdits
758
+ info = info if info else self.parseErrorHyperLinkInfo(msg)
759
+ filename = info.get("filename", "") if info else ""
760
+ isConsolePrEdit = '<ConsolePrEdit>' in filename
761
+
762
+ if info and doHyperlink and not isConsolePrEdit:
763
+ fileStart = info.get("fileStart")
764
+ fileEnd = info.get("fileEnd")
765
+ lineNum = info.get("lineNum")
766
+
767
+ isWorkbox = '<WorkboxSelection>' in filename or '<Workbox>' in filename
768
+ if isWorkbox:
769
+ split = filename.split(':')
770
+ workboxIdx = split[-1]
771
+ filename = ''
772
+ else:
773
+ filename = filename
774
+ workboxIdx = ''
775
+ href = '{}, {}, {}'.format(filename, workboxIdx, lineNum)
776
+
777
+ # Insert initial, non-underlined text
778
+ cursor.insertText(msg[:fileStart])
779
+
780
+ # Insert hyperlink
781
+ fmt = cursor.charFormat()
782
+ fmt.setAnchor(True)
783
+ fmt.setAnchorHref(href)
784
+ fmt.setFontUnderline(True)
785
+ toolTip = "Open {} at line number {}".format(filename, lineNum)
786
+ fmt.setToolTip(toolTip)
787
+ cursor.insertText(msg[fileStart:fileEnd], fmt)
788
+
789
+ # Insert the rest of the msg
790
+ fmt.setAnchor(False)
791
+ fmt.setAnchorHref('')
792
+ fmt.setFontUnderline(False)
793
+ fmt.setToolTip('')
794
+ cursor.insertText(msg[fileEnd:], fmt)
795
+ else:
796
+ # Non-hyperlink output
797
+ self.insertPlainText(msg)
798
+
799
+ # if a outputPipe was provided, write the message to that pipe
800
+ if self.outputPipe:
801
+ self.outputPipe(msg, error=error)