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,2115 +1,2039 @@
1
- ##
2
- #
3
- # \remarks This dialog allows the user to create new python classes and packages
4
- # based on plugin templates
5
- #
6
- # \author beta@blur.com
7
- # \author Blur Studio
8
- # \date 08/19/10
9
- #
10
-
11
- from __future__ import absolute_import
12
- import sys
13
- import os
14
- import os.path
15
- import six
16
-
17
- from Qt.QtCore import QFile, QTextCodec, Qt, Property, Signal, QPoint
18
- from Qt.Qsci import QsciScintilla
19
- from Qt.QtGui import QColor, QFont, QIcon, QFontMetrics
20
- from Qt.QtWidgets import (
21
- QApplication,
22
- QInputDialog,
23
- QMessageBox,
24
- QAction,
25
- QMenu,
26
- QShortcut,
27
- )
28
- from Qt import QtCompat
29
- from collections import OrderedDict
30
-
31
- import blurdev
32
- from blurdev.enum import enum
33
- from blurdev.gui import QtPropertyInit
34
- from blurdev.debug import debugMsg, DebugLevel
35
- from . import lang
36
- from .delayable_engine import DelayableEngine
37
- import re
38
- import string
39
- import time
40
-
41
-
42
- class DocumentEditor(QsciScintilla):
43
- SearchDirection = enum('First', 'Forward', 'Backward')
44
- SearchOptions = enum('Backward', 'CaseSensitive', 'WholeWords', 'QRegExp')
45
- _defaultFont = QFont()
46
- _defaultFont.fromString('Courier New,9,-1,5,50,0,0,0,1,0')
47
-
48
- fontsChanged = Signal(
49
- QFont, QFont
50
- ) # emits the font size change (font size, margin font size)
51
- documentSaved = Signal(
52
- QsciScintilla, object
53
- ) # (DocumentEditor, filename) emitted when ever the document is saved.
54
-
55
- def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
56
- super(DocumentEditor, self).__init__(parent)
57
- self.setObjectName('DocumentEditor')
58
- # Spell check variables
59
- self.__speller__ = None
60
- self.pos = None
61
- self.anchor = None
62
-
63
- # create custom properties
64
- self._filename = ''
65
- self.additionalFilenames = []
66
- self._language = ''
67
- self._lastSearch = ''
68
- self._textCodec = None
69
- self._fileMonitoringActive = False
70
- self._marginsFont = self._defaultFont
71
- self._lastSearchDirection = self.SearchDirection.First
72
- self._saveTimer = 0.0
73
- self._autoReloadOnChange = False
74
- self._enableFontResizing = True
75
- # QSci doesnt provide accessors to these values, so store them internally
76
- self._foldMarginBackgroundColor = QColor(224, 224, 224)
77
- self._foldMarginForegroundColor = QColor(Qt.white)
78
- self._marginsBackgroundColor = QColor(224, 224, 224)
79
- self._marginsForegroundColor = QColor()
80
- self._matchedBraceBackgroundColor = QColor(224, 224, 224)
81
- self._matchedBraceForegroundColor = QColor()
82
- self._unmatchedBraceBackgroundColor = QColor(Qt.white)
83
- self._unmatchedBraceForegroundColor = QColor(Qt.blue)
84
- self._caretForegroundColor = QColor()
85
- self._caretBackgroundColor = QColor(255, 255, 255, 255)
86
- self._selectionBackgroundColor = QColor(192, 192, 192)
87
- self._selectionForegroundColor = QColor(Qt.black)
88
- self._indentationGuidesBackgroundColor = QColor(Qt.white)
89
- self._indentationGuidesForegroundColor = QColor(Qt.black)
90
- self._markerBackgroundColor = QColor(Qt.white)
91
- self._markerForegroundColor = QColor(Qt.black)
92
-
93
- # Setup the DelayableEngine and add the document to it
94
- self.delayable_info = OrderedDict()
95
- self.delayable_engine = DelayableEngine.instance(delayable_engine)
96
- self.delayable_engine.add_document(self)
97
- # ------------------------------------------------------------------------------
98
- # used to store the right click location
99
- self._clickPos = None
100
- # dialog shown is used to prevent showing multiple versions of the of the
101
- # confirmation dialog. this is caused because multiple signals are emitted and
102
- # processed.
103
- self._dialogShown = False
104
- # used to store perminately highlighted keywords
105
- self._permaHighlight = []
106
- self._highlightedKeywords = ''
107
- self.setSmartHighlightingRegEx()
108
-
109
- # intialize settings
110
- self.initSettings(first_time=True)
111
-
112
- # set one time properties
113
- self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
114
- self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
115
- self.setContextMenuPolicy(Qt.CustomContextMenu)
116
- self.setAcceptDrops(False)
117
- # Not supported by older builds of QsciScintilla
118
- if hasattr(self, 'setTabDrawMode'):
119
- self.setTabDrawMode(QsciScintilla.TabStrikeOut)
120
-
121
- # create connections
122
- self.customContextMenuRequested.connect(self.showMenu)
123
- self.selectionChanged.connect(self.updateSelectionInfo)
124
- blurdev.core.styleSheetChanged.connect(self.updateColorScheme)
125
-
126
- # Create shortcuts
127
- icon = QIcon(blurdev.resourcePath('img/ide/copy.png'))
128
-
129
- # We have to re-create the copy shortcut so we can use our implementation
130
- self.uiCopyACT = QAction(icon, 'Copy', self)
131
- self.uiCopyACT.setShortcut('Ctrl+C')
132
- self.uiCopyACT.triggered.connect(self.copy)
133
- self.addAction(self.uiCopyACT)
134
-
135
- iconlstrip = QIcon(blurdev.resourcePath('img/ide/copylstrip.png'))
136
- self.uiCopyLstripACT = QAction(iconlstrip, 'Copy lstrip', self)
137
- self.uiCopyLstripACT.setShortcut('Ctrl+Shift+C')
138
- self.uiCopyLstripACT.triggered.connect(self.copyLstrip)
139
- self.addAction(self.uiCopyLstripACT)
140
-
141
- self.uiCopyHtmlACT = QAction(icon, 'Copy Html', self)
142
- self.uiCopyHtmlACT.triggered.connect(self.copyHtml)
143
- self.addAction(self.uiCopyHtmlACT)
144
-
145
- self.uiCopySpaceIndentationACT = QAction(icon, 'Copy Tabs to Spaces', self)
146
- self.uiCopySpaceIndentationACT.setShortcut('Ctrl+Shift+Space')
147
- self.uiCopySpaceIndentationACT.triggered.connect(self.copySpaceIndentation)
148
- self.addAction(self.uiCopySpaceIndentationACT)
149
-
150
- # Update keyboard shortcuts that come with QsciScintilla
151
- commands = self.standardCommands()
152
- # Remove the Ctrl+/ "Move left one word part" shortcut so it can be used to
153
- # comment
154
- command = commands.boundTo(Qt.ControlModifier | Qt.Key_Slash)
155
- if command is not None:
156
- command.setKey(0)
157
-
158
- for command in commands.commands():
159
- if command.description() == 'Move selected lines up one line':
160
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Up)
161
- if command.description() == 'Move selected lines down one line':
162
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Down)
163
- if command.description() == 'Duplicate selection':
164
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_D)
165
- if command.description() == 'Cut current line':
166
- command.setKey(0)
167
-
168
- # Add QShortcuts
169
- self.uiShowAutoCompleteSCT = QShortcut(
170
- Qt.CTRL | Qt.Key_Space, self, context=Qt.WidgetShortcut
171
- )
172
- self.uiShowAutoCompleteSCT.activated.connect(lambda: self.showAutoComplete())
173
-
174
- # load the file
175
- if filename:
176
- self.load(filename)
177
- else:
178
- self.refreshTitle()
179
- self.setLanguage('Plain Text')
180
-
181
- # goto the line
182
- if lineno:
183
- self.setCursorPosition(lineno, 0)
184
-
185
- def autoFormat(self):
186
- try:
187
- import autopep8
188
- except ImportError:
189
- QMessageBox.warning(
190
- self.window(),
191
- 'autopep8 missing',
192
- (
193
- 'The autopep8 library is missing. '
194
- 'To use this feature you must install it. '
195
- 'https://pypi.python.org/pypi/autopep8/ '
196
- ),
197
- QMessageBox.Ok,
198
- )
199
- return
200
- version = autopep8.__version__.split('.')
201
- if version and version[0] < 1:
202
- QMessageBox.warning(
203
- self.window(),
204
- 'autopep8 out of date',
205
- (
206
- 'The autopep8 library is out of date and needs to be updated. '
207
- 'To use this feature you must install it. '
208
- 'https://pypi.python.org/pypi/autopep8/ '
209
- ),
210
- QMessageBox.Ok,
211
- )
212
- return
213
- options = autopep8.parse_args([''])
214
- options.max_line_length = self.edgeColumn()
215
- fixed = autopep8.fix_code(self.text(), options=options)
216
- self.beginUndoAction()
217
- startLine, startCol, endLine, endCol = self.getSelection()
218
- self.selectAll()
219
- self.removeSelectedText()
220
- self.insert(fixed)
221
- if self.indentationsUseTabs():
222
- # fix tab indentations
223
- self.indentSelection(True)
224
- self.unindentSelection(True)
225
- self.setSelection(startLine, startCol + 1, endLine, endCol)
226
- self.endUndoAction()
227
-
228
- def autoReloadOnChange(self):
229
- return self._autoReloadOnChange
230
-
231
- def caretBackgroundColor(self):
232
- return self._caretBackgroundColor
233
-
234
- def caretForegroundColor(self):
235
- return self._caretForegroundColor
236
-
237
- def setCaretLineBackgroundColor(self, color):
238
- self._caretBackgroundColor = color
239
- super(DocumentEditor, self).setCaretLineBackgroundColor(color)
240
-
241
- def setCaretForegroundColor(self, color):
242
- self._caretForegroundColor = color
243
- super(DocumentEditor, self).setCaretForegroundColor(color)
244
-
245
- def checkForSave(self):
246
- if self.isModified():
247
- result = QMessageBox.question(
248
- self.window(),
249
- 'Save changes to...',
250
- 'Do you want to save your changes?',
251
- QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
252
- )
253
- if result == QMessageBox.Yes:
254
- return self.save()
255
- elif result == QMessageBox.Cancel:
256
- return False
257
- return True
258
-
259
- def closeEvent(self, event):
260
- self.disableTitleUpdate()
261
- # unsubcribe the file from the open file monitor
262
- self.enableFileWatching(False)
263
- super(DocumentEditor, self).closeEvent(event)
264
-
265
- def closeEditor(self):
266
- parent = self.parent()
267
- if parent and parent.inherits('QMdiSubWindow'):
268
- parent.close()
269
-
270
- def commentCheck(self):
271
- # collect the language
272
- language = lang.byName(self._language)
273
- if not language:
274
- QMessageBox.critical(
275
- self,
276
- 'No Language Defined',
277
- 'There is no language defined for this editor.',
278
- )
279
- return '', False
280
-
281
- # grab the line comment
282
- comment = language.lineComment()
283
- if not comment:
284
- QMessageBox.critical(
285
- self,
286
- 'No Line Comment Defined',
287
- 'There is no line comment symbol defined for the "%s" language.'
288
- % self._language,
289
- )
290
- return '', False
291
- return comment, True
292
-
293
- def commentToggle(self, doWhich=None):
294
- """Toggle comments, mimicing SublimeText functionality.
295
-
296
- - Comments will be indented to match the outermost line being commented.
297
- - Commenting / uncommenting is determined by whether all non-empty lines are
298
- currently commented or not. If they ALL are, then uncomment, otherwise
299
- comment.
300
- """
301
-
302
- # If called by 'triggered' signal, clear out passed argument.
303
- if not isinstance(doWhich, six.string_types):
304
- doWhich = None
305
-
306
- comment, result = self.commentCheck()
307
- if not result:
308
- return False
309
- commentSpace = comment + " "
310
-
311
- self.beginUndoAction()
312
- # lookup the selected text positions
313
- cursorLine, cursorIndex = self.expandCursorToLineSelection()
314
- startLine, startCol, endLine, endCol = self.getSelection()
315
-
316
- # Collect comments and indents, to determine indentation to use, and whether
317
- # to comment or uncomment.
318
- comments = []
319
- indents = []
320
- for line in range(startLine, endLine + 1):
321
- lineText = self.getSelectionCurrentLineText(line)
322
-
323
- # Skip if line is empty, or line is last line without selection
324
- if not lineText.strip() or (line == endLine and not endCol):
325
- continue
326
-
327
- comments.append(lineText.lstrip()[0] == comment)
328
-
329
- curIndent = self.determineIndent(lineText, comment)
330
- indents.append(curIndent)
331
-
332
- if not indents:
333
- return
334
- indent = min(indents)
335
-
336
- # If all lines are comments, we uncomment. If any aren't comments, we comment.
337
- if doWhich is None:
338
- if all(comments):
339
- doWhich = "Uncomment"
340
- else:
341
- doWhich = "Comment"
342
-
343
- for line in range(startLine, endLine + 1):
344
- lineText = self.getSelectionCurrentLineText(line)
345
- if not lineText.strip():
346
- continue
347
-
348
- # Do not toggle comments on the last line if it contains no selection
349
- if line != endLine or endCol:
350
-
351
- if doWhich == "Comment":
352
- self.setCursorPosition(line, indent)
353
- self.insert(commentSpace)
354
- if cursorIndex is not None and cursorIndex >= indent:
355
- cursorIndex += len(commentSpace)
356
- if line == startLine:
357
- startCol -= len(commentSpace)
358
- if line == endLine:
359
- endCol += len(commentSpace)
360
-
361
- elif doWhich == "Uncomment":
362
- for curComment in [commentSpace, comment]:
363
- foundText = self.getSelectedCommentText(
364
- line, indent, len(curComment)
365
- )
366
- startCol, endCol, cursorIndex, removed = self.removeComment(
367
- foundText,
368
- curComment,
369
- line,
370
- indent,
371
- startLine,
372
- startCol,
373
- endLine,
374
- endCol,
375
- cursorIndex,
376
- )
377
- if removed:
378
- break
379
-
380
- # restore the currently selected text, or cursor position
381
- if cursorLine is not None:
382
- startLine, endLine = cursorLine, cursorLine
383
- startCol, endCol = cursorIndex, cursorIndex
384
- self.setSelection(startLine, startCol, endLine, endCol)
385
- self.endUndoAction()
386
- # return True
387
-
388
- def removeComment(
389
- self,
390
- text,
391
- comment,
392
- line,
393
- indent,
394
- startLine,
395
- startCol,
396
- endLine,
397
- endCol,
398
- cursorIndex,
399
- ):
400
- removed = False
401
- if text == comment:
402
- commentLen = len(comment)
403
- self.setSelection(line, indent, line, indent + commentLen)
404
- self.removeSelectedText()
405
-
406
- if cursorIndex > indent:
407
- adjustment = None
408
- for checkIndex in range(commentLen - 1):
409
- newIndex = indent + checkIndex + 1
410
- if cursorIndex == newIndex:
411
- adjustment = checkIndex + 1
412
- break
413
- if adjustment is None:
414
- adjustment = commentLen
415
- cursorIndex -= adjustment
416
-
417
- if line == startLine:
418
- startCol -= commentLen
419
- if line == endLine:
420
- endCol -= commentLen
421
-
422
- removed = True
423
- return startCol, endCol, cursorIndex, removed
424
-
425
- def determineIndent(self, lineText, comment=None):
426
- indent = len(lineText) - len(lineText.lstrip())
427
- return indent
428
-
429
- def getSelectedCommentText(self, line, indent, commentLen):
430
- """Because QScintilla.setSelection automatically strips trailing
431
- whitespace, we grab the whole rest of the line, then reset it
432
- to just the length of the currentComment
433
- """
434
- self.setSelection(line, indent, line, self.lineLength(line))
435
- text = self.selectedText()
436
- if len(text) >= commentLen:
437
- text = text[:commentLen]
438
- return text
439
-
440
- def getSelectionCurrentLineText(self, line):
441
- lineLength = len(self.text(line).rstrip())
442
- self.setSelection(line, 0, line, lineLength)
443
- lineText = self.selectedText()
444
- return lineText
445
-
446
- def expandCursorToLineSelection(self):
447
- line, index = None, None
448
- if not self.hasSelectedText():
449
- line, index = self.getCursorPosition()
450
- self.setSelection(line, 0, line, self.lineLength(line) - 2)
451
- return line, index
452
-
453
- def copy(self):
454
- """Copies the selected text.
455
-
456
- If copyIndentsAsSpaces and self.indentationsUseTabs() is True it will convert
457
- any indents to spaces before copying the text.
458
- """
459
- if self.copyIndentsAsSpaces and self.indentationsUseTabs():
460
- self.copySpaceIndentation()
461
- else:
462
- super(DocumentEditor, self).copy()
463
-
464
- def copyFilenameToClipboard(self):
465
- QApplication.clipboard().setText(self._filename)
466
-
467
- def copyLineReference(self):
468
- sel = self.getSelection()
469
- # Note: getSelection is 0 based like all good code
470
- if sel[0] == -1 and self._clickPos:
471
- lines = (self.lineAt(self.mapFromGlobal(self._clickPos)) + 1, -1)
472
- else:
473
- end = sel[2]
474
- if sel[3] == 0:
475
- # if nothing is selected on the last line, exclude it
476
- end -= 1
477
- lines = (sel[0] + 1, end + 1)
478
- args = {'filename': self.filename(), 'plural': ''}
479
- if lines[1] == -1 or lines[0] == lines[1]:
480
- args['line'] = lines[0]
481
- else:
482
- args['line'] = '{}-{}'.format(*lines)
483
- args['plural'] = 's'
484
- QApplication.clipboard().setText(
485
- '{filename}: Line{plural} {line}'.format(**args)
486
- )
487
-
488
- def copyLstrip(self):
489
- """Copy's the selected text, but strips off any leading whitespace shared by the
490
- entire selection.
491
- """
492
- start, s, end, e = self.getSelection()
493
- count = end - start + 1
494
- self.setSelection(start, 0, end, e)
495
- txt = self.selectedText()
496
-
497
- def replacement(match):
498
- return re.sub('[ \t]', '', match.group(), count=1)
499
-
500
- # NOTE: Don't use re.M, it does not support mac line endings.
501
- regex = re.compile('(?:^|\r\n?|\n)[ \t]')
502
- while len(regex.findall(txt)) == count:
503
- # We found the same number of leading whitespace as lines of text.
504
- # This means that it all has leading whitespace that needs removed.
505
- txt = regex.sub(replacement, txt)
506
- QApplication.clipboard().setText(txt)
507
-
508
- def copySpaceIndentation(self):
509
- """Copy the selected text with any tab indents converted to space indents.
510
-
511
- If indentationsUseTabs is False it will just copy the text
512
- """
513
- txt = self.selectedText()
514
-
515
- def replacement(match):
516
- return match.group().replace('\t', ' ' * self.tabWidth())
517
-
518
- # NOTE: Don't use re.M, it does not support mac line endings.
519
- ret = re.sub('(?:^|\r\n?|\n)\t+', replacement, txt)
520
- QApplication.clipboard().setText(ret)
521
-
522
- def copyHtml(self):
523
- """Copy's the selected text, but formats it using pygments if installed into
524
- html."""
525
- text = self.selectedText()
526
- from blurdev.utils.errorEmail import highlightCodeHtml
527
-
528
- text = highlightCodeHtml(text, self.language(), None)
529
- QApplication.clipboard().setText(text)
530
-
531
- def detectEndLine(self, text):
532
- newlineN = text.find('\n')
533
- newlineR = text.find('\r')
534
- if newlineN != -1 and newlineR != -1:
535
- if newlineN == newlineR + 1:
536
- # CR LF Windows
537
- return self.EolWindows
538
- elif newlineR == newlineN + 1:
539
- # LF CR ACorn and RISC unsuported
540
- return self.eolMode()
541
- if newlineN != -1 and newlineR != -1:
542
- if newlineN < newlineR:
543
- # First return is a LF
544
- return self.EolUnix
545
- else:
546
- # first return is a CR
547
- return self.EolMac
548
- if newlineN != -1:
549
- return self.EolUnix
550
- if sys.platform == 'win32':
551
- return self.EolWindows
552
- return self.EolUnix
553
-
554
- def editPermaHighlight(self):
555
- text, success = QInputDialog.getText(
556
- self,
557
- 'Edit PermaHighlight keywords',
558
- 'Add keywords separated by a space',
559
- text=' '.join(self.permaHighlight()),
560
- )
561
- if success:
562
- self.setPermaHighlight(text.split(' '))
563
-
564
- def enableFileWatching(self, state):
565
- """Enables/Disables open file change monitoring. If enabled, A dialog will pop
566
- up when ever the open file is changed externally. If file monitoring is
567
- disabled in the IDE settings it will be ignored.
568
-
569
- Returns:
570
- bool:
571
- """
572
- # if file monitoring is enabled and we have a file name then set up the file
573
- # monitoring
574
- window = self.window()
575
- self._fileMonitoringActive = False
576
- if hasattr(window, 'openFileMonitor'):
577
- fm = window.openFileMonitor()
578
- if fm:
579
- if state:
580
- fm.addPath(self._filename)
581
- self._fileMonitoringActive = True
582
- else:
583
- fm.removePath(self._filename)
584
- return self._fileMonitoringActive
585
-
586
- def disableTitleUpdate(self):
587
- self.modificationChanged.connect(self.refreshTitle)
588
-
589
- def enableTitleUpdate(self):
590
- self.modificationChanged.connect(self.refreshTitle)
591
-
592
- def eventFilter(self, object, event):
593
- if event.type() == event.Close and not self.checkForSave():
594
- event.ignore()
595
- return True
596
- return False
597
-
598
- def exploreDocument(self):
599
- path = self._filename
600
- if os.path.isfile(path):
601
- path = os.path.split(path)[0]
602
-
603
- if os.path.exists(path):
604
- blurdev.osystem.explore(path)
605
- else:
606
- QMessageBox.critical(
607
- self, 'Missing Path', 'Could not find %s' % path.replace('/', '\\')
608
- )
609
-
610
- def execStandalone(self):
611
- if self.save():
612
- os.startfile(str(self.filename()))
613
-
614
- def foldMarginColors(self):
615
- """Returns the fold margin's foreground and background QColor
616
-
617
- Returns:
618
- foreground(QColor): The foreground color
619
- background(QColor): The background color
620
- """
621
- return self._foldMarginForegroundColor, self._foldMarginBackgroundColor
622
-
623
- def setFoldMarginColors(self, foreground, background):
624
- """Sets the fold margins foreground and background QColor
625
-
626
- Args:
627
- foreground(QColor): The forground color of the checkerboard
628
- background(QColor): The background color of the checkerboard
629
- """
630
- self._foldMarginForegroundColor = foreground
631
- self._foldMarginBackgroundColor = background
632
- super(DocumentEditor, self).setFoldMarginColors(foreground, background)
633
-
634
- def goToLine(self, line=None):
635
- if type(line) != int:
636
- line, accepted = QInputDialog.getInt(self, 'Line Number', 'Line:')
637
- else:
638
- accepted = True
639
-
640
- if accepted:
641
- # MH 04/12/11 changed from line + 1 to line - 1 to make the gotoLine dialog
642
- # go to the correct line.
643
- self.setCursorPosition(line - 1, 0)
644
- self.ensureLineVisible(line)
645
-
646
- def goToDefinition(self, text=None):
647
- if not text:
648
- text = self.selectedText()
649
- if not text:
650
- text, accepted = QInputDialog.getText(self, 'def Name', 'Name:')
651
- else:
652
- accepted = True
653
- else:
654
- accepted = True
655
- if accepted:
656
- descriptors = lang.byName(self.language()).descriptors()
657
- docText = self.text()
658
- for descriptor in descriptors:
659
- result = descriptor.search(docText)
660
- while result:
661
- name = result.group('name')
662
- if name.startswith(text):
663
- self.findNext(name, 0)
664
- return
665
- result = descriptor.search(docText, result.end())
666
-
667
- def language(self):
668
- return self._language
669
-
670
- def languageChosen(self, action):
671
- self.setLanguage(action.text())
672
- self.updateColorScheme()
673
- self._fileMonitoringActive = False
674
- window = self.window()
675
- if hasattr(window, 'uiLanguageDDL'):
676
- window.uiLanguageDDL.blockSignals(True)
677
- window.uiLanguageDDL.setCurrentLanguage(action.text())
678
- window.uiLanguageDDL.blockSignals(False)
679
-
680
- def lineMarginWidth(self):
681
- return self.marginWidth(self.SymbolMargin)
682
-
683
- def load(self, filename):
684
- filename = str(filename)
685
- if filename and os.path.exists(filename):
686
- f = QFile(filename)
687
- f.open(QFile.ReadOnly)
688
- text = f.readAll()
689
- self._textCodec = QTextCodec.codecForUtfText(
690
- text, QTextCodec.codecForName('UTF-8')
691
- )
692
- self.setText(self._textCodec.toUnicode(text))
693
- f.close()
694
- self.updateFilename(filename)
695
- self.enableFileWatching(True)
696
- self.setEolMode(self.detectEndLine(self.text()))
697
- return True
698
- return False
699
-
700
- def filename(self):
701
- return self._filename
702
-
703
- def findNext(self, text, flags):
704
- re = (flags & self.SearchOptions.QRegExp) != 0
705
- cs = (flags & self.SearchOptions.CaseSensitive) != 0
706
- wo = (flags & self.SearchOptions.WholeWords) != 0
707
- wrap = True
708
- forward = True
709
-
710
- result = self.findFirst(text, re, cs, wo, wrap, forward)
711
-
712
- if not result:
713
- self.findTextNotFound(text)
714
-
715
- return result
716
-
717
- def findPrev(self, text, flags):
718
- re = (flags & self.SearchOptions.QRegExp) != 0
719
- cs = (flags & self.SearchOptions.CaseSensitive) != 0
720
- wo = (flags & self.SearchOptions.WholeWords) != 0
721
- wrap = True
722
- forward = False
723
-
724
- isSelected = self.hasSelectedText()
725
- result = self.findFirst(text, re, cs, wo, wrap, forward)
726
- if result and isSelected:
727
- # If text is selected when finding previous, it will find the currently
728
- # selected text so do another find.
729
- result = QsciScintilla.findNext(self)
730
-
731
- if not result:
732
- self.findTextNotFound(text)
733
-
734
- return result
735
-
736
- def find_simple(self, find_state):
737
- """Python implementation of QsciScintilla.simpleFind.
738
-
739
- Args:
740
- find_state (blurdev.scintilla.FindState): A find state used to manage the find.
741
-
742
- https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
743
- """
744
- if find_state.start_pos == find_state.end_pos:
745
- return -1
746
-
747
- self.SendScintilla(self.SCI_SETTARGETSTART, find_state.start_pos)
748
- self.SendScintilla(self.SCI_SETTARGETEND, find_state.end_pos)
749
-
750
- # scintilla can't match unicode strings, even in python 3
751
- # In python 3 you have to cast it to a bytes object
752
- expr = bytes(str(find_state.expr).encode("utf-8"))
753
-
754
- return self.SendScintilla(self.SCI_SEARCHINTARGET, len(expr), expr)
755
-
756
- def find_text(self, find_state):
757
- """Finds text in the document without changing the selection.
758
-
759
- Args:
760
- find_state (blurdev.scintilla.FindState): A find state used to manage the find.
761
-
762
- Based on QsciScintilla.doFind.
763
- https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
764
- """
765
- # Set the search flags
766
- self.SendScintilla(self.SCI_SETSEARCHFLAGS, find_state.flags)
767
- # If no end was specified, use the end of the document
768
- if find_state.end_pos is None:
769
- find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
770
-
771
- pos = self.find_simple(find_state)
772
-
773
- # See if it was found. If not and wraparound is wanted, try again.
774
- if pos == -1 and find_state.wrap:
775
- if find_state.forward:
776
- find_state.start_pos = 0
777
- if find_state.start_pos_original is None:
778
- find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
779
- else:
780
- find_state.end_pos = find_state.start_pos_original
781
- else:
782
- if find_state.start_pos_original is None:
783
- find_state.start_pos = self.SendScintilla(self.SCI_GETLENGTH)
784
- else:
785
- find_state.start_pos = find_state.start_pos_original
786
- find_state.end_pos = 0
787
- # Give a indication that we have wrapped
788
- find_state.wrapped = True
789
-
790
- pos = self.find_simple(find_state)
791
-
792
- if pos == -1:
793
- return -1, 0
794
-
795
- # It was found.
796
- target_start = self.SendScintilla(self.SCI_GETTARGETSTART)
797
- target_end = self.SendScintilla(self.SCI_GETTARGETEND)
798
-
799
- # Finally adjust the start position so that we don't find the same one again.
800
- if find_state.forward:
801
- find_state.start_pos = target_end
802
- else:
803
- find_state.start_pos = target_start - 1
804
- if find_state.start_pos < 0:
805
- find_state.start_pos = 0
806
-
807
- return target_start, target_end
808
-
809
- def find_text_from_cursor(self, find_state):
810
- """Starting from the current cursor position wrapping around, return all
811
- matches to the provided find_state.
812
-
813
- Args:
814
- find_state (blurdev.scintilla.FindState): A find state used to manage the find.
815
- """
816
- # Start searching from the cursor, wrap past the end and stop where we started
817
- current_position = self.positionFromLineIndex(*self.getCursorPosition())
818
- find_state.start_pos = current_position
819
- find_state.start_pos_original = current_position
820
-
821
- positions = []
822
- start, end = self.find_text(find_state)
823
- while start != -1:
824
- positions.append((start, end))
825
- if find_state.wrapped:
826
- # once we have wrapped, disable wrap
827
- find_state.wrap = False
828
- start, end = self.find_text(find_state)
829
- return positions
830
-
831
- def findTextNotFound(self, text):
832
- try:
833
- # If a number was typed in, ask the user if they wanted to goto that line
834
- # number.
835
- line = int(text)
836
- msg = (
837
- 'Search string "%s" was not found. \nIt looks like a line number, '
838
- 'would you like to goto line %i?'
839
- )
840
- result = QMessageBox.critical(
841
- self,
842
- 'No Text Found',
843
- msg % (text, line),
844
- buttons=(QMessageBox.Yes | QMessageBox.No),
845
- defaultButton=QMessageBox.Yes,
846
- )
847
- if result == QMessageBox.Yes:
848
- self.goToLine(line)
849
- except ValueError:
850
- QMessageBox.critical(
851
- self, 'No Text Found', 'Search string "%s" was not found.' % text
852
- )
853
-
854
- def keyPressEvent(self, event):
855
- key = event.key()
856
- if key == Qt.Key_Backtab:
857
- self.unindentSelection()
858
- elif key == Qt.Key_Escape:
859
- # Using QShortcut for Escape did not seem to work.
860
- self.showAutoComplete(True)
861
- else:
862
- return QsciScintilla.keyPressEvent(self, event)
863
-
864
- def keyReleaseEvent(self, event):
865
- if event.key() == Qt.Key_Menu:
866
- # Calculate the screen coordinates of the text cursor.
867
- position = self.positionFromLineIndex(*self.getCursorPosition())
868
- x = self.SendScintilla(self.SCI_POINTXFROMPOSITION, 0, position)
869
- y = self.SendScintilla(self.SCI_POINTYFROMPOSITION, 0, position)
870
- # When using the menu key, show the right click menu at the text
871
- # cursor, not the mouse cursor, it is not in the correct place.
872
- self.showMenu(QPoint(x, y))
873
- else:
874
- return super(DocumentEditor, self).keyReleaseEvent(event)
875
-
876
- def initSettings(self, first_time=False):
877
- """Set/reset settings using the IDE section settings."""
878
-
879
- # set visibility settings
880
- self.setAutoIndent(True)
881
- if first_time:
882
- self.setIndentationsUseTabs(True)
883
- self.setTabIndents(True)
884
- self.setTabWidth(4)
885
- self.setCaretLineVisible(False)
886
- self.setShowWhitespaces(False)
887
- self.setMarginLineNumbers(0, True)
888
- self.setIndentationGuides(False)
889
- self.setEolVisibility(False)
890
- self.setShowSmartHighlighting(True)
891
- self.setBackspaceUnindents(True)
892
-
893
- self.setEdgeMode(self.EdgeNone)
894
-
895
- # set autocompletion settings
896
- self.setAutoCompletionSource(QsciScintilla.AcsAll)
897
- self.setAutoCompletionThreshold(3)
898
-
899
- self.setFont(self.documentFont)
900
- self.setMarginsFont(self.marginsFont())
901
- self.setMarginWidth(0, QFontMetrics(self.marginsFont()).width('0000000') + 5)
902
-
903
- def markerNext(self):
904
- line, index = self.getCursorPosition()
905
- newline = self.markerFindNext(line + 1, self.marginMarkerMask(1))
906
-
907
- # wrap around the document if necessary
908
- if newline == -1:
909
- newline = self.markerFindNext(0, self.marginMarkerMask(1))
910
-
911
- self.setCursorPosition(newline, index)
912
-
913
- def markerLoad(self, input):
914
- r"""
915
- \remarks Takes a list of line numbers and adds a marker to each of them
916
- in the file.
917
- """
918
- for line in input:
919
- marker = self.markerDefine(self.Circle)
920
- self.markerAdd(line, marker)
921
-
922
- def markerToggle(self):
923
- line, index = self.getCursorPosition()
924
- markers = self.markersAtLine(line)
925
- if not markers:
926
- marker = self.markerDefine(self.Circle)
927
- self.markerAdd(line, marker)
928
- else:
929
- self.markerDelete(line)
930
-
931
- def marginsFont(self):
932
- return self._marginsFont
933
-
934
- def multipleSelection(self):
935
- """Returns if multiple selection is enabled."""
936
- return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
937
-
938
- def multipleSelectionAdditionalSelectionTyping(self):
939
- """Returns if multiple selection allows additional typing."""
940
- return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
941
-
942
- def multipleSelectionMultiPaste(self):
943
- """Paste into all multiple selections."""
944
- return self.SendScintilla(self.SCI_GETMULTIPASTE)
945
-
946
- def paste(self):
947
- text = QApplication.clipboard().text()
948
- if text.find('\n') == -1 and text.find('\r') == -1:
949
- return super(DocumentEditor, self).paste()
950
-
951
- def repForMode(mode):
952
- if mode == self.EolWindows:
953
- return '\r\n'
954
- elif mode == self.EolUnix:
955
- return '\n'
956
- else:
957
- return '\r'
958
-
959
- text = text.replace(
960
- repForMode(self.detectEndLine(text)), repForMode(self.eolMode())
961
- )
962
- QApplication.clipboard().setText(text)
963
- return super(DocumentEditor, self).paste()
964
-
965
- def permaHighlight(self):
966
- return self._permaHighlight
967
-
968
- def setPermaHighlight(self, value):
969
- if not isinstance(value, list):
970
- raise TypeError('PermaHighlight must be a list')
971
-
972
- # lexer = self.lexer()
973
- # if self._smartHighlightingSupported:
974
- # self._permaHighlight = value
975
- # self.setHighlightedKeywords(lexer, self._highlightedKeywords)
976
- # else:
977
- # raise TypeError('PermaHighlight is not supported by this lexer.')
978
-
979
- def refreshToolTip(self):
980
- # TODO: This will proably be removed once I add a user interface to
981
- # additionalFilenames.
982
- toolTip = []
983
- if self.additionalFilenames:
984
- toolTip.append('<u><b>Additional Filenames:</b></u>')
985
- for filename in self.additionalFilenames:
986
- toolTip.append(filename)
987
- self.setToolTip('\n<br>'.join(toolTip))
988
-
989
- def reloadFile(self):
990
- return self.reloadDialog(
991
- 'Are you sure you want to reload %s? You will lose all changes'
992
- % os.path.basename(self.filename())
993
- )
994
-
995
- def reloadChange(self):
996
- """Callback for file monitoring. If a file was modified or deleted this method
997
- is called when Open File Monitoring is enabled. Returns if the file was updated
998
- or left open
999
-
1000
- Returns:
1001
- bool:
1002
- """
1003
- debugMsg(
1004
- 'Reload Change called: %0.3f Dialog Shown: %r'
1005
- % (self._saveTimer, self._dialogShown),
1006
- DebugLevel.High,
1007
- )
1008
- if time.time() - self._saveTimer < 0.5:
1009
- # If we are saving no need to reload the file
1010
- debugMsg('timer has not expired', DebugLevel.High)
1011
- return False
1012
- if not os.path.isfile(self.filename()) and not self._dialogShown:
1013
- debugMsg('The file was deleted', DebugLevel.High)
1014
- # the file was deleted, ask the user if they still want to keep the file in
1015
- # the editor.
1016
- self._dialogShown = True
1017
- result = QMessageBox.question(
1018
- self.window(),
1019
- 'File Removed...',
1020
- 'File: %s has been deleted.\nKeep file in editor?' % self.filename(),
1021
- QMessageBox.Yes,
1022
- QMessageBox.No,
1023
- )
1024
- self._dialogShown = False
1025
- if result == QMessageBox.No:
1026
- debugMsg(
1027
- 'The file was deleted, removing document from editor',
1028
- DebugLevel.High,
1029
- )
1030
- self.parent().close()
1031
- return False
1032
- # TODO: The file no longer exists, and the document should be marked as
1033
- # changed.
1034
- debugMsg(
1035
- 'The file was deleted, But the user left it in the editor',
1036
- DebugLevel.High,
1037
- )
1038
- self.enableFileWatching(False)
1039
- return True
1040
- debugMsg('Defaulting to reload message', DebugLevel.High)
1041
- return self.reloadDialog(
1042
- 'File: %s has been changed.\nReload from disk?' % self.filename()
1043
- )
1044
-
1045
- def reloadDialog(self, message, title='Reload File...'):
1046
- if not self._dialogShown:
1047
- self._dialogShown = True
1048
- if self._autoReloadOnChange or not self.isModified():
1049
- result = QMessageBox.Yes
1050
- else:
1051
- result = QMessageBox.question(
1052
- self.window(), title, message, QMessageBox.Yes | QMessageBox.No
1053
- )
1054
- self._dialogShown = False
1055
- if result == QMessageBox.Yes:
1056
- return self.load(self.filename())
1057
- return False
1058
-
1059
- def replace(self, text, searchtext=None, all=False):
1060
- # replace the current text with the inputed text
1061
- if not searchtext:
1062
- searchtext = self.selectedText()
1063
-
1064
- # make sure something is selected
1065
- if not searchtext:
1066
- return 0
1067
-
1068
- self.beginUndoAction()
1069
- sel = self.getSelection()
1070
-
1071
- # replace all of the instances of the text
1072
- if all:
1073
- count = self.text().count(searchtext, Qt.CaseInsensitive)
1074
- found = 0
1075
- while self.findFirst(searchtext, False, False, False, True, True):
1076
- if found == count:
1077
- # replaced all items, exit so we don't get a infinite loop
1078
- break
1079
- found += 1
1080
- super(DocumentEditor, self).replace(text)
1081
-
1082
- # replace a single instance of the text
1083
- else:
1084
- count = 1
1085
- super(DocumentEditor, self).replace(text)
1086
-
1087
- self.setSelection(*sel)
1088
- self.endUndoAction()
1089
-
1090
- return count
1091
-
1092
- def setText(self, text):
1093
- self.blockSignals(True)
1094
- super(DocumentEditor, self).setText(text)
1095
- self.blockSignals(False)
1096
- self.spellCheck(0, None)
1097
-
1098
- def refreshTitle(self):
1099
- try:
1100
- parent = self.parent()
1101
- if parent and parent.inherits('QMdiSubWindow'):
1102
- parent.setWindowTitle(self.windowTitle())
1103
- except RuntimeError:
1104
- pass
1105
-
1106
- def save(self):
1107
- debugMsg(
1108
- ' Saved Called'.center(60, '-'),
1109
- DebugLevel.High,
1110
- )
1111
- ret = self.saveAs(self.filename())
1112
- # If the user has provided additionalFilenames to save, process each of them
1113
- # without switching the current filename.
1114
- for filename in self.additionalFilenames:
1115
- self.saveAs(filename, setFilename=False)
1116
- return ret
1117
-
1118
- def saveAs(self, filename='', setFilename=True):
1119
- debugMsg(
1120
- ' Save As Called '.center(60, '-'),
1121
- DebugLevel.High,
1122
- )
1123
- newFile = False
1124
- if not filename:
1125
- newFile = True
1126
- filename = self.filename()
1127
- filename, extFilter = QtCompat.QFileDialog.getSaveFileName(
1128
- self.window(), 'Save File as...', filename
1129
- )
1130
-
1131
- if filename:
1132
- self._saveTimer = time.time()
1133
- # save the file to disk
1134
- f = QFile(filename)
1135
- f.open(QFile.WriteOnly)
1136
- # make sure the file is writeable
1137
- if f.error() != QFile.NoError:
1138
- debugMsg('An error occured while saving', DebugLevel.High)
1139
- QMessageBox.question(
1140
- self.window(),
1141
- 'Error saving file...',
1142
- 'There was a error saving the file. Error Code: %i' % f.error(),
1143
- QMessageBox.Ok,
1144
- )
1145
- f.close()
1146
- return False
1147
- # Attempt to save the file using the same codec that it used to display it
1148
- if self._textCodec:
1149
- f.write(self._textCodec.fromUnicode(self.text()))
1150
- else:
1151
- self.write(f)
1152
- f.close()
1153
- # notify that the document was saved
1154
- self.documentSaved.emit(self, filename)
1155
-
1156
- # update the file
1157
- if setFilename:
1158
- self.updateFilename(filename)
1159
- if newFile:
1160
- self.enableFileWatching(True)
1161
- return True
1162
- return False
1163
-
1164
- def selectProjectItem(self):
1165
- window = self.window()
1166
- if window:
1167
- window.selectProjectItem(self.filename())
1168
-
1169
- def selectionBackgroundColor(self):
1170
- return self._selectionBackgroundColor
1171
-
1172
- def setSelectionBackgroundColor(self, color):
1173
- self._selectionBackgroundColor = color
1174
- super(DocumentEditor, self).setSelectionBackgroundColor(color)
1175
-
1176
- def selectionForegroundColor(self):
1177
- return self._selectionForegroundColor
1178
-
1179
- def setSelectionForegroundColor(self, color):
1180
- self._selectionForegroundColor = color
1181
- super(DocumentEditor, self).setSelectionForegroundColor(color)
1182
-
1183
- def selection_is_word(self):
1184
- """Checks if the current selection is a single word.
1185
-
1186
- Returns:
1187
- bool: The selected text is a single word.
1188
- """
1189
- sel = self.getSelection()
1190
- start = self.positionFromLineIndex(*sel[:2])
1191
- end = self.positionFromLineIndex(*sel[2:])
1192
- return self.is_word(start, end)
1193
-
1194
- def is_word(self, start, end):
1195
- """Checks if the text between start and end position is a word
1196
-
1197
- Args:
1198
- start (int): Start of text offset index position.
1199
- end (int): End of text offset index position.
1200
-
1201
- Returns:
1202
- bool: The text between the start and end position is a single word.
1203
- """
1204
- if start == end:
1205
- return False
1206
- # Get the word at the start of selection, if the selection doesn't match
1207
- # its not a word.
1208
- start_pos = self.SendScintilla(self.SCI_WORDSTARTPOSITION, start, True)
1209
- end_pos = self.SendScintilla(self.SCI_WORDENDPOSITION, start, True)
1210
-
1211
- return start == start_pos and end == end_pos
1212
-
1213
- def setLanguage(self, language):
1214
- if language == 'Plain Text':
1215
- language = ''
1216
- # grab the language from the lang module if it is a string
1217
- if type(language) != lang.Language:
1218
- language = str(language)
1219
- language = lang.byName(language)
1220
-
1221
- # collect the language's lexer
1222
- if language:
1223
- lexer = language.createLexer(self)
1224
- self._language = language.name()
1225
- else:
1226
- lexer = None
1227
- self._language = ''
1228
-
1229
- # set the lexer & init the settings
1230
- self.setLexer(lexer)
1231
- self.initSettings()
1232
-
1233
- # Add language keywords to aspell session dictionary
1234
- if self.spellCheckEnabled():
1235
- self.delayable_engine.delayables['spell_check'].reset_session(self)
1236
-
1237
- def setLexer(self, lexer):
1238
- font = self.documentFont
1239
- if lexer:
1240
- font = lexer.font(0)
1241
- # Backup values destroyed when we set the lexer
1242
- marginFont = self.marginsFont()
1243
- folds = self.contractedFolds()
1244
- super(DocumentEditor, self).setLexer(lexer)
1245
- # Restore values destroyed when we set the lexer
1246
- self.setContractedFolds(folds)
1247
- self.setMarginsFont(marginFont)
1248
- self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1249
- self.setMarginsForegroundColor(self.marginsForegroundColor())
1250
- self.setFoldMarginColors(*self.foldMarginColors())
1251
- self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1252
- self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1253
- if lexer:
1254
- lexer.setColor(
1255
- self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE
1256
- )
1257
- lexer.setPaper(
1258
- self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE
1259
- )
1260
- # QSciLexer.wordCharacters is not virtual, or even exposed. This hack allows
1261
- # custom lexers to define their own wordCharacters
1262
- if hasattr(lexer, 'wordCharactersOverride'):
1263
- wordCharacters = lexer.wordCharactersOverride
1264
- else:
1265
- # We can't query the lexer for its word characters, but we can query the
1266
- # document. This ensures the lexer's wordCharacters are used if switching
1267
- # from a wordCharactersOverride lexer to a lexer that doesn't define custom
1268
- # wordCharacters.
1269
- wordCharacters = self.wordCharacters()
1270
- self.SendScintilla(self.SCI_SETWORDCHARS, wordCharacters.encode('utf8'))
1271
-
1272
- if lexer:
1273
- lexer.setFont(font)
1274
- else:
1275
- self.setFont(font)
1276
-
1277
- def setLineMarginWidth(self, width):
1278
- self.setMarginWidth(self.SymbolMargin, width)
1279
-
1280
- def setMarginsFont(self, font):
1281
- super(DocumentEditor, self).setMarginsFont(font)
1282
- self._marginsFont = font
1283
-
1284
- def setMultipleSelection(self, state):
1285
- """Enables or disables multiple selection
1286
-
1287
- Args:
1288
- state (bool): Enable or disable multiple selection. When multiple
1289
- selection is disabled, it is not possible to select multiple
1290
- ranges by holding down the Ctrl key while dragging with the
1291
- mouse.
1292
- """
1293
- self.SendScintilla(self.SCI_SETMULTIPLESELECTION, state)
1294
-
1295
- def setMultipleSelectionAdditionalSelectionTyping(self, state):
1296
- """Enables or disables multiple selection allows additional typing.
1297
-
1298
- Args:
1299
- state (bool): Whether typing, new line, cursor left/right/up/down,
1300
- backspace, delete, home, and end work with multiple selections
1301
- simultaneously. Also allows selection and word and line
1302
- deletion commands.
1303
- """
1304
- self.SendScintilla(self.SCI_SETADDITIONALSELECTIONTYPING, state)
1305
-
1306
- def setMultipleSelectionMultiPaste(self, state):
1307
- """Enables or disables multiple selection allows additional typing.
1308
-
1309
- Args:
1310
- state (int): When pasting into multiple selections, the pasted text
1311
- can go into just the main selection with self.SC_MULTIPASTE_ONCE or
1312
- into each selection with self.SC_MULTIPASTE_EACH.
1313
- self.SC_MULTIPASTE_ONCE is the default.
1314
- """
1315
- self.SendScintilla(self.SCI_SETMULTIPASTE, state)
1316
-
1317
- def setSmartHighlightingRegEx(
1318
- self, exp=r'[ \t\n\r\.,?;:!()\[\]+\-\*\/#@^%$"\\~&{}|=<>\']'
1319
- ):
1320
- """Set the regular expression used to control if a selection is considered
1321
- valid for smart highlighting.
1322
-
1323
- Args:
1324
- exp (str):
1325
- """
1326
- self._smartHighlightingRegEx = exp
1327
- self.selectionValidator = re.compile(exp)
1328
-
1329
- def setShowFolding(self, state):
1330
- if state:
1331
- self.setFolding(self.BoxedTreeFoldStyle)
1332
- else:
1333
- self.setFolding(self.NoFoldStyle)
1334
-
1335
- def setShowLineNumbers(self, state):
1336
- self.setMarginLineNumbers(self.SymbolMargin, state)
1337
-
1338
- def setShowSmartHighlighting(self, state):
1339
- self.delayable_engine.set_delayable_enabled('smart_highlight', state)
1340
-
1341
- def setShowWhitespaces(self, state):
1342
- if state:
1343
- self.setWhitespaceVisibility(QsciScintilla.WsVisible)
1344
- else:
1345
- self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
1346
-
1347
- def spellCheckEnabled(self):
1348
- """Is spellcheck is enabled for this document."""
1349
- return self.delayable_engine.delayable_enabled('spell_check')
1350
-
1351
- def setSpellCheckEnabled(self, state):
1352
- """Enable/disable spellcheck if spellcheck can be enabled.
1353
- This changes spellcheck for all documents attached to this
1354
- documents delayable_engine.
1355
- """
1356
- self.delayable_engine.set_delayable_enabled('spell_check', state)
1357
-
1358
- def addWordToDict(self, word):
1359
- self.__speller__.addtoPersonal(word)
1360
- self.__speller__.saveAllwords()
1361
- self.spellCheck(0, None)
1362
- self.pos += len(word)
1363
- self.SendScintilla(self.SCI_GOTOPOS, self.pos)
1364
-
1365
- def correctSpelling(self, action):
1366
- self.SendScintilla(self.SCI_GOTOPOS, self.pos)
1367
- self.SendScintilla(self.SCI_SETANCHOR, self.anchor)
1368
- self.beginUndoAction()
1369
- self.SendScintilla(self.SCI_REPLACESEL, action.text())
1370
- self.endUndoAction()
1371
-
1372
- def spellCheck(self, start_pos, end_pos):
1373
- """Check spelling for some text in the document.
1374
-
1375
- Args:
1376
- start_pos (int): The document position to start spell checking.
1377
- end_pos (int): The document position to stop spell checking.
1378
-
1379
- Returns:
1380
- int: Returns 0 if spell check is finished. 1 if additional
1381
- processing is scheduled. 2 if the spell check was canceled
1382
- because the widget is not visible.
1383
- """
1384
- self.delayable_engine.enqueue(self, 'spell_check', start_pos, end_pos)
1385
-
1386
- def onTextModified(
1387
- self,
1388
- pos,
1389
- mtype,
1390
- text,
1391
- length,
1392
- linesAdded,
1393
- line,
1394
- foldNow,
1395
- foldPrev,
1396
- token,
1397
- annotationLinesAdded,
1398
- ):
1399
- if self.spellCheckEnabled() and (
1400
- (mtype & self.SC_MOD_INSERTTEXT) == self.SC_MOD_INSERTTEXT
1401
- or (mtype & self.SC_MOD_DELETETEXT) == self.SC_MOD_DELETETEXT
1402
- ):
1403
- # Only spell-check if text was inserted/deleted
1404
- line = self.SendScintilla(self.SCI_LINEFROMPOSITION, pos)
1405
- # More than one line could have been inserted.
1406
- # If this number is negative it will cause Qt to crash.
1407
- lines_to_check = line + max(0, linesAdded)
1408
- self.spellCheck(
1409
- self.SendScintilla(self.SCI_POSITIONFROMLINE, line),
1410
- self.SendScintilla(self.SCI_GETLINEENDPOSITION, lines_to_check),
1411
- )
1412
-
1413
- def showAutoComplete(self, toggle=False):
1414
- # if using autoComplete toggle the autoComplete list
1415
- if self.autoCompletionSource() == QsciScintilla.AcsAll:
1416
- if self.isListActive(): # is the autoComplete list visible
1417
- if toggle:
1418
- self.cancelList() # Close the autoComplete list
1419
- else:
1420
- self.autoCompleteFromAll() # Show the autoComplete list
1421
-
1422
- def showMenu(self, pos):
1423
- menu = QMenu(self)
1424
- pos = self.mapToGlobal(pos)
1425
- self._clickPos = pos
1426
-
1427
- if self.spellCheckEnabled():
1428
- # Get the word under the mouse and split the word if camelCase
1429
- point = self.mapFromGlobal(self._clickPos)
1430
- x = point.x()
1431
- y = point.y()
1432
- wordUnderMouse = self.wordAtPoint(point)
1433
- positionMouse = self.SendScintilla(self.SCI_POSITIONFROMPOINT, x, y)
1434
- wordStartPosition = self.SendScintilla(
1435
- self.SCI_WORDSTARTPOSITION, positionMouse, True
1436
- )
1437
- spell_check = self.delayable_engine.delayables['spell_check']
1438
- results = spell_check.chunk_re.findall(
1439
- self.text(wordStartPosition, wordStartPosition + len(wordUnderMouse))
1440
- )
1441
-
1442
- for space, wordChunk in results:
1443
- camel_case_words = spell_check.camel_case_split(wordChunk)
1444
- lengthSpace = len(space)
1445
- for word in camel_case_words:
1446
- lengthWord = len(word)
1447
- # Calcualate the actual word start position accounting for any
1448
- # non-alpha chars word_new_start_position = wordStartPosition +
1449
- # lengthSpace
1450
- if (
1451
- wordStartPosition + lengthSpace <= positionMouse
1452
- and wordStartPosition + lengthSpace + lengthWord > positionMouse
1453
- and not any(letter in string.digits for letter in word)
1454
- and not self.__speller__.check(word)
1455
- ):
1456
- # For camelCase words, get the exact word under the mouse
1457
- self.pos = wordStartPosition + lengthSpace
1458
- self.anchor = wordStartPosition + lengthSpace + lengthWord
1459
- # Add spelling suggestions to menu
1460
- submenu = menu.addMenu(word)
1461
- submenu.setObjectName('uiSpellCheckMENU')
1462
- wordSuggestionList = self.__speller__.suggest(word)
1463
- for wordSuggestion in wordSuggestionList:
1464
- act = submenu.addAction(wordSuggestion)
1465
- submenu.triggered.connect(self.correctSpelling)
1466
- addmenu = menu.addAction('Add %s to dictionary' % word)
1467
- addmenu.triggered.connect(lambda: self.addWordToDict(word))
1468
- addmenu.setObjectName('uiSpellCheckAddWordACT')
1469
- menu.addSeparator()
1470
- break
1471
- else:
1472
- wordStartPosition += lengthWord
1473
- wordStartPosition += lengthSpace
1474
-
1475
- act = menu.addAction('Goto')
1476
- # act.setShortcut('Ctrl+G')
1477
- act.triggered.connect(self.goToLine)
1478
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/goto.png')))
1479
- act = menu.addAction('Go to Definition')
1480
- # act.setShortcut('Ctrl+Shift+G')
1481
- act.triggered.connect(self.goToDefinition)
1482
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/goto_def.png')))
1483
- if self.showSmartHighlighting():
1484
- act = menu.addAction('Edit PermaHighlight')
1485
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/highlighter.png')))
1486
- act.triggered.connect(self.editPermaHighlight)
1487
-
1488
- menu.addSeparator()
1489
-
1490
- act = menu.addAction('Collapse/Expand All')
1491
- act.triggered.connect(self.toggleFolding)
1492
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/plus_minus.png')))
1493
-
1494
- menu.addSeparator()
1495
-
1496
- act = menu.addAction('Cut')
1497
- act.triggered.connect(self.cut)
1498
- act.setShortcut('Ctrl+X')
1499
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/cut.png')))
1500
-
1501
- act = menu.addAction('Copy')
1502
- act.triggered.connect(self.copy)
1503
- act.setShortcut('Ctrl+C')
1504
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/copy.png')))
1505
-
1506
- copyMenu = menu.addMenu('Advanced Copy')
1507
-
1508
- # Note: I cant use the actions defined above because they end up getting garbage
1509
- # collected
1510
- iconlstrip = QIcon(blurdev.resourcePath('img/ide/copylstrip.png'))
1511
- act = QAction(iconlstrip, 'Copy lstrip', copyMenu)
1512
- act.setShortcut('Ctrl+Shift+C')
1513
- act.triggered.connect(self.copyLstrip)
1514
- copyMenu.addAction(act)
1515
-
1516
- icon = QIcon(blurdev.resourcePath('img/ide/copy.png'))
1517
- act = QAction(icon, 'Copy Html', copyMenu)
1518
- act.triggered.connect(self.copyHtml)
1519
- copyMenu.addAction(act)
1520
-
1521
- act = QAction(icon, 'Copy Tabs to Spaces', copyMenu)
1522
- act.setShortcut('Ctrl+Shift+Space')
1523
- act.triggered.connect(self.copySpaceIndentation)
1524
- copyMenu.addAction(act)
1525
-
1526
- act = menu.addAction('Paste')
1527
- act.triggered.connect(self.paste)
1528
- act.setShortcut('Ctrl+V')
1529
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/paste.png')))
1530
-
1531
- menu.addSeparator()
1532
-
1533
- act = menu.addAction('Copy Line Reference')
1534
- act.triggered.connect(self.copyLineReference)
1535
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/copy.png')))
1536
-
1537
- menu.addSeparator()
1538
-
1539
- act = menu.addAction('Comment Toggle')
1540
- act.triggered.connect(self.commentToggle)
1541
- act.setShortcut("Ctrl+/")
1542
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/comment_toggle.png')))
1543
-
1544
- menu.addSeparator()
1545
-
1546
- act = menu.addAction('To Lowercase')
1547
- act.triggered.connect(self.toLower)
1548
- # act.setShortcut('Ctrl+L')
1549
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/lowercase.png')))
1550
- act = menu.addAction('To Uppercase')
1551
- act.triggered.connect(self.toUpper)
1552
- # act.setShortcut('Ctrl+U')
1553
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/uppercase.png')))
1554
-
1555
- menu.addSeparator()
1556
-
1557
- submenu = menu.addMenu('View as...')
1558
- submenu.setIcon(QIcon(blurdev.resourcePath('img/ide/view_as.png')))
1559
- lg = self.language()
1560
- act = submenu.addAction('Plain Text')
1561
- if lg == "":
1562
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/check.png')))
1563
- submenu.addSeparator()
1564
-
1565
- for language in lang.languages():
1566
- act = submenu.addAction(language)
1567
- if language == lg:
1568
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/check.png')))
1569
-
1570
- submenu.triggered.connect(self.languageChosen)
1571
-
1572
- menu.addSeparator()
1573
-
1574
- act = menu.addAction('Indent using tabs')
1575
- act.triggered.connect(self.setIndentationsUseTabs)
1576
- act.setCheckable(True)
1577
- act.setChecked(self.indentationsUseTabs())
1578
-
1579
- if self._fileMonitoringActive:
1580
- act = menu.addAction('Auto Reload file')
1581
- act.triggered.connect(self.setAutoReloadOnChange)
1582
- act.setCheckable(True)
1583
- act.setChecked(self._autoReloadOnChange)
1584
-
1585
- if self.language() == 'Python':
1586
- menu.addSeparator()
1587
- act = menu.addAction('Autoformat (PEP 8)')
1588
- act.triggered.connect(self.autoFormat)
1589
- act.setIcon(QIcon(blurdev.resourcePath('img/ide/python.png')))
1590
-
1591
- menu.popup(self._clickPos)
1592
-
1593
- def showEvent(self, event):
1594
- super(DocumentEditor, self).showEvent(event)
1595
- # Update the colorScheme after the stylesheet has been fully loaded.
1596
- self.updateColorScheme()
1597
-
1598
- def showFolding(self):
1599
- return self.folding() != self.NoFoldStyle
1600
-
1601
- def showLineNumbers(self):
1602
- return self.marginLineNumbers(self.SymbolMargin)
1603
-
1604
- def showSmartHighlighting(self):
1605
- return self.delayable_engine.delayable_enabled('smart_highlight')
1606
-
1607
- def showWhitespaces(self):
1608
- return self.whitespaceVisibility() == QsciScintilla.WsVisible
1609
-
1610
- def smartHighlightingRegEx(self):
1611
- return self._smartHighlightingRegEx
1612
-
1613
- def toLower(self):
1614
- self.beginUndoAction()
1615
- lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1616
- text = self.selectedText().lower()
1617
- self.removeSelectedText()
1618
- self.insert(text)
1619
- self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1620
- self.endUndoAction()
1621
-
1622
- def toggleFolding(self):
1623
- self.foldAll(QApplication.instance().keyboardModifiers() == Qt.ShiftModifier)
1624
-
1625
- def toUpper(self):
1626
- self.beginUndoAction()
1627
- lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1628
- text = self.selectedText().upper()
1629
- self.removeSelectedText()
1630
- self.insert(text)
1631
- self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1632
- self.endUndoAction()
1633
-
1634
- def updateColorScheme(self):
1635
- """Sets the DocumentEditor's lexer colors, see colorScheme for a compatible
1636
- dict
1637
- """
1638
- # lookup the language
1639
- language = lang.byName(self.language())
1640
- lex = self.lexer()
1641
- if not lex:
1642
- self.setPaper(self.paperDefault)
1643
- self.setColor(self.colorDefault)
1644
- return
1645
- # Backup the lexer font. The calls to setPaper/setColor cause it to be reset.
1646
- font = lex.font(0)
1647
- # Set Default lexer colors
1648
- for i in range(128):
1649
- lex.setPaper(self.paperDefault, i)
1650
- lex.setColor(self.colorDefault, i)
1651
- lex.setDefaultPaper(self.paperDefault)
1652
- lex.setDefaultColor(self.colorDefault)
1653
- # Override lexer color/paper values
1654
- if language:
1655
- _lexerColorNames = set(
1656
- [
1657
- x.replace('color', '')
1658
- for x in dir(self)
1659
- if x.startswith('color') and x.replace('color', '')
1660
- ]
1661
- )
1662
- for colorName, keys in language.lexerColorTypes().items():
1663
- color = None
1664
- paper = None
1665
- if colorName == 'misc':
1666
- color = self.colorDefault
1667
- paper = self.paperDefault
1668
- else:
1669
- for name in _lexerColorNames:
1670
- if name.lower() == colorName:
1671
- color = getattr(self, 'color{}'.format(name))
1672
- paper = getattr(self, 'paper{}'.format(name))
1673
- break
1674
- for key in keys:
1675
- if paper:
1676
- lex.setPaper(paper, key)
1677
- if color:
1678
- lex.setColor(color, key)
1679
- lex.setColor(self.braceBadForeground, self.STYLE_BRACEBAD)
1680
- lex.setPaper(self.braceBadBackground, self.STYLE_BRACEBAD)
1681
- lex.setColor(self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE)
1682
- lex.setPaper(self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE)
1683
- # Update other values stored in the lexer
1684
- self.setFoldMarginColors(
1685
- self.foldMarginsForegroundColor, self.foldMarginsBackgroundColor
1686
- )
1687
- self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1688
- self.setMarginsForegroundColor(self.marginsForegroundColor())
1689
- self.setFoldMarginColors(*self.foldMarginColors())
1690
- self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1691
- self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1692
- # Restore the existing font
1693
- lex.setFont(font, 0)
1694
-
1695
- def updateFilename(self, filename):
1696
- filename = str(filename)
1697
- extension = os.path.splitext(filename)[1]
1698
-
1699
- # determine if we need to modify the language
1700
- if not self._filename or extension != os.path.splitext(self._filename)[1]:
1701
- self.setLanguage(lang.byExtension(extension))
1702
-
1703
- # update the filename information
1704
- self._filename = os.path.abspath(filename)
1705
- self.setModified(False)
1706
-
1707
- try:
1708
- self.window().emitDocumentTitleChanged()
1709
- except Exception:
1710
- pass
1711
-
1712
- self.refreshTitle()
1713
-
1714
- def updateHighlighter(self):
1715
- # Get selection
1716
- selectedText = self.selectedText()
1717
- # if text is selected make sure it is a word
1718
- lexer = self.lexer()
1719
- if selectedText != lexer.highlightedKeywords:
1720
- if selectedText:
1721
- validator = self.selectionValidator
1722
- if hasattr(lexer, 'selectionValidator'):
1723
- # If a lexer has defined its own selectionValidator use that instead
1724
- validator = lexer.selectionValidator
1725
- # Does the text contain a non allowed word?
1726
- if not validator.findall(selectedText) == []:
1727
- return
1728
- else:
1729
- selection = self.getSelection()
1730
- # the character before and after the selection must not be a word.
1731
- text = self.text(selection[2]) # Character after
1732
- if selection[3] < len(text):
1733
- if validator.findall(text[selection[3]]) == []:
1734
- return
1735
- text = self.text(selection[0]) # Character Before
1736
- if selection[1] and selection[1] != -1:
1737
- if validator.findall(text[selection[1] - 1]) == []:
1738
- return
1739
- # Make the lexer highlight words
1740
- self.setHighlightedKeywords(lexer, selectedText)
1741
-
1742
- def updateSelectionInfo(self):
1743
- window = self.window()
1744
- if window and hasattr(window, 'uiCursorInfoLBL'):
1745
- sline, spos, eline, epos = self.getSelection()
1746
- # Add 1 to line numbers because document line numbers are 1 based
1747
- text = ''
1748
- if sline == -1:
1749
- line, pos = self.getCursorPosition()
1750
- line += 1
1751
- text = 'Line: {} Pos: {}'.format(line, pos)
1752
- else:
1753
- sline += 1
1754
- eline += 1
1755
- text = (
1756
- 'Line: {sline} Pos: {spos} To Line: {eline} '
1757
- 'Pos: {epos} Line Count: {lineCount}'
1758
- )
1759
- text = text.format(
1760
- sline=sline,
1761
- spos=spos,
1762
- eline=eline,
1763
- epos=epos,
1764
- lineCount=eline - sline + 1,
1765
- )
1766
- if self._textCodec and self._textCodec.name() != 'System':
1767
- text = 'Encoding: {enc} {text}'.format(
1768
- enc=self._textCodec.name(), text=text
1769
- )
1770
- window.uiCursorInfoLBL.setText(text)
1771
-
1772
- def setAutoReloadOnChange(self, state):
1773
- self._autoReloadOnChange = state
1774
-
1775
- def setHighlightedKeywords(self, lexer, keywords):
1776
- """Updates the lexers highlighted keywords
1777
-
1778
- Args:
1779
- lexer (QSciLexer): Update this lexer and set as the lexer on the document.
1780
- keywords (str): keywords to highlight
1781
- """
1782
-
1783
- # self.updateColorScheme()
1784
- # self._highlightedKeywords = keywords
1785
- # lexer.highlightedKeywords = ' '.join(self._permaHighlight + [keywords])
1786
- #
1787
- # # Clearing the lexer before re-setting the lexer seems to fix the
1788
- # scroll/jump issue
1789
- # # when using smartHighlighting near the end of the document.
1790
- # self.setLexer(None)
1791
- # self.setLexer(lexer)
1792
- # # repaint appears to fix the problem with text being squashed when
1793
- # smartHighlighting
1794
- # # is activated by clicking and draging to select text.
1795
- # self.repaint()
1796
-
1797
- def indentSelection(self, all=False):
1798
- if all:
1799
- lineFrom = 0
1800
- lineTo = self.lines()
1801
- else:
1802
- lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1803
- self.beginUndoAction()
1804
- for line in range(lineFrom, lineTo + 1):
1805
- self.indent(line)
1806
- self.endUndoAction()
1807
-
1808
- def unindentSelection(self, all=False):
1809
- if all:
1810
- lineFrom = 0
1811
- lineTo = self.lines()
1812
- else:
1813
- lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1814
- self.beginUndoAction()
1815
- for line in range(lineFrom, lineTo + 1):
1816
- self.unindent(line)
1817
- self.endUndoAction()
1818
-
1819
- def windowTitle(self):
1820
- if self._filename:
1821
- title = os.path.basename(self._filename)
1822
- else:
1823
- title = 'New Document'
1824
-
1825
- if self.isModified():
1826
- title += '*'
1827
-
1828
- if self.additionalFilenames:
1829
- title = '[{}]'.format(title)
1830
-
1831
- return title
1832
-
1833
- def wheelEvent(self, event):
1834
- if self._enableFontResizing and event.modifiers() == Qt.ControlModifier:
1835
- # If used in LoggerWindow, use that wheel event
1836
- # May not want to import LoggerWindow, so perhaps
1837
- # check by str(type())
1838
- # if isinstance(self.window(), "LoggerWindow"):
1839
- if "LoggerWindow" in str(type(self.window())):
1840
- self.window().wheelEvent(event)
1841
- return
1842
-
1843
- font = self.documentFont
1844
- marginsFont = self.marginsFont()
1845
- lexer = self.lexer()
1846
- if lexer:
1847
- font = lexer.font(0)
1848
- try:
1849
- # Qt5 support
1850
- delta = event.angleDelta().y()
1851
- except Exception:
1852
- # Qt4 support
1853
- delta = event.delta()
1854
- if delta > 0:
1855
- font.setPointSize(font.pointSize() + 1)
1856
- marginsFont.setPointSize(marginsFont.pointSize() + 1)
1857
- else:
1858
- if font.pointSize() - 1 > 0:
1859
- font.setPointSize(font.pointSize() - 1)
1860
- if marginsFont.pointSize() - 1 > 0:
1861
- marginsFont.setPointSize(marginsFont.pointSize() - 1)
1862
-
1863
- self.setMarginsFont(marginsFont)
1864
- if lexer:
1865
- lexer.setFont(font)
1866
- else:
1867
- self.setFont(font)
1868
-
1869
- self.fontsChanged.emit(font, marginsFont)
1870
- event.accept()
1871
- else:
1872
- super(DocumentEditor, self).wheelEvent(event)
1873
-
1874
- # expose properties for the designer
1875
- pyLanguage = Property("QString", language, setLanguage)
1876
- pyLineMarginWidth = Property("int", lineMarginWidth, setLineMarginWidth)
1877
- pyShowLineNumbers = Property("bool", showLineNumbers, setShowLineNumbers)
1878
- pyShowFolding = Property("bool", showFolding, setShowFolding)
1879
- pyShowSmartHighlighting = Property(
1880
- "bool", showSmartHighlighting, setShowSmartHighlighting
1881
- )
1882
- pySmartHighlightingRegEx = Property(
1883
- "QString", smartHighlightingRegEx, setSmartHighlightingRegEx
1884
- )
1885
-
1886
- pyAutoCompletionCaseSensitivity = Property(
1887
- "bool",
1888
- QsciScintilla.autoCompletionCaseSensitivity,
1889
- QsciScintilla.setAutoCompletionCaseSensitivity,
1890
- )
1891
- pyAutoCompletionReplaceWord = Property(
1892
- "bool",
1893
- QsciScintilla.autoCompletionReplaceWord,
1894
- QsciScintilla.setAutoCompletionReplaceWord,
1895
- )
1896
- pyAutoCompletionShowSingle = Property(
1897
- "bool",
1898
- QsciScintilla.autoCompletionShowSingle,
1899
- QsciScintilla.setAutoCompletionShowSingle,
1900
- )
1901
- pyAutoCompletionThreshold = Property(
1902
- "int",
1903
- QsciScintilla.autoCompletionThreshold,
1904
- QsciScintilla.setAutoCompletionThreshold,
1905
- )
1906
- pyAutoIndent = Property(
1907
- "bool", QsciScintilla.autoIndent, QsciScintilla.setAutoIndent
1908
- )
1909
- pyBackspaceUnindents = Property(
1910
- "bool", QsciScintilla.backspaceUnindents, QsciScintilla.setBackspaceUnindents
1911
- )
1912
- pyIndentationGuides = Property(
1913
- "bool", QsciScintilla.indentationGuides, QsciScintilla.setIndentationGuides
1914
- )
1915
- pyIndentationsUseTabs = Property(
1916
- "bool", QsciScintilla.indentationsUseTabs, QsciScintilla.setIndentationsUseTabs
1917
- )
1918
- pyTabIndents = Property(
1919
- "bool", QsciScintilla.tabIndents, QsciScintilla.setTabIndents
1920
- )
1921
- pyUtf8 = Property("bool", QsciScintilla.isUtf8, QsciScintilla.setUtf8)
1922
- pyWhitespaceVisibility = Property(
1923
- "bool",
1924
- QsciScintilla.whitespaceVisibility,
1925
- QsciScintilla.setWhitespaceVisibility,
1926
- )
1927
-
1928
- # Color Setters required because QSci doesn't expose getters.
1929
- # --------------------------------------------------------------------------------
1930
- def edgeColor(self):
1931
- """This is subclassed so we can create a Property of it"""
1932
- return super(DocumentEditor, self).edgeColor()
1933
-
1934
- def setEdgeColor(self, color):
1935
- """This is subclassed so we can create a Property of it"""
1936
- super(DocumentEditor, self).setEdgeColor(color)
1937
-
1938
- # Because foreground and background must be set together, this cant use
1939
- # QtPropertyInit
1940
- @Property(QColor)
1941
- def foldMarginsBackgroundColor(self):
1942
- return self._foldMarginBackgroundColor
1943
-
1944
- @foldMarginsBackgroundColor.setter
1945
- def foldMarginsBackgroundColor(self, color):
1946
- self._foldMarginBackgroundColor = color
1947
- self.setFoldMarginColors(self._foldMarginForegroundColor, color)
1948
-
1949
- @Property(QColor)
1950
- def foldMarginsForegroundColor(self):
1951
- return self._foldMarginForegroundColor
1952
-
1953
- @foldMarginsForegroundColor.setter
1954
- def foldMarginsForegroundColor(self, color):
1955
- self._foldMarginForegroundColor = color
1956
- self.setFoldMarginColors(color, self._foldMarginBackgroundColor)
1957
-
1958
- def indentationGuidesBackgroundColor(self):
1959
- return self._indentationGuidesBackgroundColor
1960
-
1961
- def setIndentationGuidesBackgroundColor(self, color):
1962
- self._indentationGuidesBackgroundColor = color
1963
- super(DocumentEditor, self).setIndentationGuidesBackgroundColor(color)
1964
-
1965
- def indentationGuidesForegroundColor(self):
1966
- return self._indentationGuidesForegroundColor
1967
-
1968
- def setIndentationGuidesForegroundColor(self, color):
1969
- self._indentationGuidesForegroundColor = color
1970
- super(DocumentEditor, self).setIndentationGuidesForegroundColor(color)
1971
-
1972
- def marginsBackgroundColor(self):
1973
- return self._marginsBackgroundColor
1974
-
1975
- def setMarginsBackgroundColor(self, color):
1976
- self._marginsBackgroundColor = color
1977
- super(DocumentEditor, self).setMarginsBackgroundColor(color)
1978
-
1979
- def marginsForegroundColor(self):
1980
- return self._marginsForegroundColor
1981
-
1982
- def setMarginsForegroundColor(self, color):
1983
- self._marginsForegroundColor = color
1984
- super(DocumentEditor, self).setMarginsForegroundColor(color)
1985
-
1986
- def matchedBraceBackgroundColor(self):
1987
- return self._matchedBraceBackgroundColor
1988
-
1989
- def matchedBraceForegroundColor(self):
1990
- return self._matchedBraceForegroundColor
1991
-
1992
- def setMatchedBraceBackgroundColor(self, color):
1993
- self._matchedBraceBackgroundColor = color
1994
- super(DocumentEditor, self).setMatchedBraceBackgroundColor(color)
1995
-
1996
- def setMatchedBraceForegroundColor(self, color):
1997
- self._matchedBraceForegroundColor = color
1998
- super(DocumentEditor, self).setMatchedBraceForegroundColor(color)
1999
-
2000
- def markerBackgroundColor(self):
2001
- return self._markerBackgroundColor
2002
-
2003
- def setMarkerBackgroundColor(self, color):
2004
- self._markerBackgroundColor = color
2005
- super(DocumentEditor, self).setMarkerBackgroundColor(color)
2006
-
2007
- def markerForegroundColor(self):
2008
- return self._markerForegroundColor
2009
-
2010
- def setMarkerForegroundColor(self, color):
2011
- self._markerForegroundColor = color
2012
- super(DocumentEditor, self).setMarkerForegroundColor(color)
2013
-
2014
- def unmatchedBraceBackgroundColor(self):
2015
- return self._unmatchedBraceBackgroundColor
2016
-
2017
- def setUnmatchedBraceBackgroundColor(self, color):
2018
- self._unmatchedBraceBackgroundColor = color
2019
- super(DocumentEditor, self).setUnmatchedBraceBackgroundColor(color)
2020
-
2021
- def unmatchedBraceForegroundColor(self):
2022
- return self._unmatchedBraceForegroundColor
2023
-
2024
- def setUnmatchedBraceForegroundColor(self, color):
2025
- self._unmatchedBraceForegroundColor = color
2026
- super(DocumentEditor, self).setUnmatchedBraceForegroundColor(color)
2027
-
2028
- # Handle Stylesheet colors for properties that are built into QsciScintilla but dont
2029
- # have getters.
2030
- pyMarginsBackgroundColor = Property(
2031
- QColor, marginsBackgroundColor, setMarginsBackgroundColor
2032
- )
2033
- pyMarginsForegroundColor = Property(
2034
- QColor, marginsForegroundColor, setMarginsForegroundColor
2035
- )
2036
- pyMatchedBraceBackgroundColor = Property(
2037
- QColor, matchedBraceBackgroundColor, setMatchedBraceBackgroundColor
2038
- )
2039
- pyMatchedBraceForegroundColor = Property(
2040
- QColor, matchedBraceForegroundColor, setMatchedBraceForegroundColor
2041
- )
2042
- pyCaretBackgroundColor = Property(
2043
- QColor, caretBackgroundColor, setCaretLineBackgroundColor
2044
- )
2045
- pyCaretForegroundColor = Property(
2046
- QColor, caretForegroundColor, setCaretForegroundColor
2047
- )
2048
- pySelectionBackgroundColor = Property(
2049
- QColor, selectionBackgroundColor, setSelectionBackgroundColor
2050
- )
2051
- pySelectionForegroundColor = Property(
2052
- QColor, selectionForegroundColor, setSelectionForegroundColor
2053
- )
2054
- pyIndentationGuidesBackgroundColor = Property(
2055
- QColor, indentationGuidesBackgroundColor, setIndentationGuidesBackgroundColor
2056
- )
2057
- pyIndentationGuidesForegroundColor = Property(
2058
- QColor, indentationGuidesForegroundColor, setIndentationGuidesForegroundColor
2059
- )
2060
- pyMarkerBackgroundColor = Property(
2061
- QColor, markerBackgroundColor, setMarkerBackgroundColor
2062
- )
2063
- pyMarkerForegroundColor = Property(
2064
- QColor, markerForegroundColor, setMarkerForegroundColor
2065
- )
2066
- pyUnmatchedBraceBackgroundColor = Property(
2067
- QColor, unmatchedBraceBackgroundColor, setUnmatchedBraceBackgroundColor
2068
- )
2069
- pyUnmatchedBraceForegroundColor = Property(
2070
- QColor, unmatchedBraceForegroundColor, setUnmatchedBraceForegroundColor
2071
- )
2072
- pyEdgeColor = Property(QColor, edgeColor, setEdgeColor)
2073
- documentFont = QtPropertyInit('_documentFont', _defaultFont)
2074
- pyMarginsFont = Property(QFont, marginsFont, setMarginsFont)
2075
-
2076
- copyIndentsAsSpaces = QtPropertyInit('_copyIndentsAsSpaces', False)
2077
-
2078
- # These colors are purely defined in DocumentEditor so we can use QtPropertyInit
2079
- braceBadForeground = QtPropertyInit('_braceBadForeground', QColor(255, 255, 255))
2080
- braceBadBackground = QtPropertyInit('_braceBadBackground', QColor(100, 60, 60))
2081
-
2082
- colorDefault = QtPropertyInit('_colorDefault', QColor())
2083
- colorComment = QtPropertyInit('_colorComment', QColor(0, 127, 0))
2084
- colorNumber = QtPropertyInit('_colorNumber', QColor(0, 127, 127))
2085
- colorString = QtPropertyInit('_colorString', QColor(127, 0, 127))
2086
- colorKeyword = QtPropertyInit('_colorKeyword', QColor(0, 0, 127))
2087
- colorTripleQuotedString = QtPropertyInit(
2088
- '_colorTripleQuotedString', QColor(127, 0, 0)
2089
- )
2090
- colorMethod = QtPropertyInit('_colorMethod', QColor(0, 0, 255))
2091
- colorFunction = QtPropertyInit('_colorFunction', QColor(0, 127, 127))
2092
- colorOperator = QtPropertyInit('_colorOperator', QColor(0, 0, 0))
2093
- colorIdentifier = QtPropertyInit('_colorIdentifier', QColor(0, 0, 0))
2094
- colorCommentBlock = QtPropertyInit('_colorCommentBlock', QColor(127, 127, 127))
2095
- colorUnclosedString = QtPropertyInit('_colorUnclosedString', QColor(0, 0, 0))
2096
- colorSmartHighlight = QtPropertyInit('_colorSmartHighlight', QColor(64, 112, 144))
2097
- colorDecorator = QtPropertyInit('_colorDecorator', QColor(128, 80, 0))
2098
-
2099
- _defaultPaper = QColor(255, 255, 255)
2100
- paperDefault = QtPropertyInit('_paperDefault', _defaultPaper)
2101
- paperComment = QtPropertyInit('_paperComment', _defaultPaper)
2102
- paperNumber = QtPropertyInit('_paperNumber', _defaultPaper)
2103
- paperString = QtPropertyInit('_paperString', _defaultPaper)
2104
- paperKeyword = QtPropertyInit('_paperKeyword', _defaultPaper)
2105
- paperTripleQuotedString = QtPropertyInit('_paperTripleQuotedString', _defaultPaper)
2106
- paperMethod = QtPropertyInit('_paperMethod', _defaultPaper)
2107
- paperFunction = QtPropertyInit('_paperFunction', _defaultPaper)
2108
- paperOperator = QtPropertyInit('_paperOperator', _defaultPaper)
2109
- paperIdentifier = QtPropertyInit('_paperIdentifier', _defaultPaper)
2110
- paperCommentBlock = QtPropertyInit('_paperCommentBlock', _defaultPaper)
2111
- paperUnclosedString = QtPropertyInit('_paperUnclosedString', QColor(224, 192, 224))
2112
- paperSmartHighlight = QtPropertyInit(
2113
- '_paperSmartHighlight', QColor(155, 255, 155, 75)
2114
- )
2115
- paperDecorator = QtPropertyInit('_paperDecorator', _defaultPaper)
1
+ ##
2
+ #
3
+ # \remarks This dialog allows the user to create new python classes and packages
4
+ # based on plugin templates
5
+ #
6
+ # \author beta@blur.com
7
+ # \author Blur Studio
8
+ # \date 08/19/10
9
+ #
10
+ from __future__ import absolute_import
11
+
12
+ import logging
13
+ import os.path
14
+ import re
15
+ import string
16
+ import sys
17
+ import time
18
+ from collections import OrderedDict
19
+ from contextlib import contextmanager
20
+ from functools import partial
21
+
22
+ import six
23
+ from PyQt5.Qsci import QsciScintilla
24
+ from Qt import QtCompat
25
+ from Qt.QtCore import Property, QFile, QPoint, Qt, QTextCodec, Signal
26
+ from Qt.QtGui import QColor, QFont, QFontMetrics, QIcon
27
+ from Qt.QtWidgets import (
28
+ QAction,
29
+ QApplication,
30
+ QInputDialog,
31
+ QMenu,
32
+ QMessageBox,
33
+ QShortcut,
34
+ )
35
+
36
+ from .. import osystem, resourcePath
37
+ from ..delayable_engine import DelayableEngine
38
+ from ..enum import Enum, EnumGroup
39
+ from ..gui import QtPropertyInit
40
+ from . import lang
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ class SearchDirection(EnumGroup):
46
+ First = Enum()
47
+ Forward = Enum()
48
+ Backward = Enum()
49
+
50
+
51
+ class SearchOptions(EnumGroup):
52
+ Backward = Enum()
53
+ CaseSensitive = Enum()
54
+ WholeWords = Enum()
55
+ QRegExp = Enum()
56
+
57
+
58
+ @contextmanager
59
+ def undo_step(editor):
60
+ """Context manager that combines all changes performed inside it as a
61
+ single undo action for the document editor."""
62
+ editor.beginUndoAction()
63
+ try:
64
+ yield
65
+ finally:
66
+ editor.endUndoAction()
67
+
68
+
69
+ class DocumentEditor(QsciScintilla):
70
+ _defaultFont = QFont()
71
+ _defaultFont.fromString('Courier New,9,-1,5,50,0,0,0,1,0')
72
+
73
+ fontsChanged = Signal(
74
+ QFont, QFont
75
+ ) # emits the font size change (font size, margin font size)
76
+ documentSaved = Signal(
77
+ QsciScintilla, object
78
+ ) # (DocumentEditor, filename) emitted when ever the document is saved.
79
+
80
+ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
81
+ super(DocumentEditor, self).__init__(parent)
82
+ self.setObjectName('DocumentEditor')
83
+ # Spell check variables
84
+ self.__speller__ = None
85
+ self.pos = None
86
+ self.anchor = None
87
+
88
+ # create custom properties
89
+ self._filename = ''
90
+ self.additionalFilenames = []
91
+ self._language = ''
92
+ self._lastSearch = ''
93
+ self._textCodec = None
94
+ self._fileMonitoringActive = False
95
+ self._marginsFont = self._defaultFont
96
+ self._lastSearchDirection = SearchDirection.First
97
+ self._saveTimer = 0.0
98
+ self._autoReloadOnChange = False
99
+ self._enableFontResizing = True
100
+ # QSci doesnt provide accessors to these values, so store them internally
101
+ self._foldMarginBackgroundColor = QColor(224, 224, 224)
102
+ self._foldMarginForegroundColor = QColor(Qt.white)
103
+ self._marginsBackgroundColor = QColor(224, 224, 224)
104
+ self._marginsForegroundColor = QColor()
105
+ self._matchedBraceBackgroundColor = QColor(224, 224, 224)
106
+ self._matchedBraceForegroundColor = QColor()
107
+ self._unmatchedBraceBackgroundColor = QColor(Qt.white)
108
+ self._unmatchedBraceForegroundColor = QColor(Qt.blue)
109
+ self._caretForegroundColor = QColor()
110
+ self._caretBackgroundColor = QColor(255, 255, 255, 255)
111
+ self._selectionBackgroundColor = QColor(192, 192, 192)
112
+ self._selectionForegroundColor = QColor(Qt.black)
113
+ self._indentationGuidesBackgroundColor = QColor(Qt.white)
114
+ self._indentationGuidesForegroundColor = QColor(Qt.black)
115
+ self._markerBackgroundColor = QColor(Qt.white)
116
+ self._markerForegroundColor = QColor(Qt.black)
117
+
118
+ # Setup the DelayableEngine and add the document to it
119
+ self.delayable_info = OrderedDict()
120
+ self.delayable_engine = DelayableEngine.instance(delayable_engine)
121
+ self.delayable_engine.add_document(self)
122
+ # ------------------------------------------------------------------------------
123
+ # used to store the right click location
124
+ self._clickPos = None
125
+ # dialog shown is used to prevent showing multiple versions of the of the
126
+ # confirmation dialog. this is caused because multiple signals are emitted and
127
+ # processed.
128
+ self._dialogShown = False
129
+ # used to store perminately highlighted keywords
130
+ self._permaHighlight = []
131
+ self.setSmartHighlightingRegEx()
132
+
133
+ # intialize settings
134
+ self.initSettings(first_time=True)
135
+
136
+ # set one time properties
137
+ self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
138
+ self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
139
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
140
+ self.setAcceptDrops(False)
141
+ # Not supported by older builds of QsciScintilla
142
+ if hasattr(self, 'setTabDrawMode'):
143
+ self.setTabDrawMode(QsciScintilla.TabStrikeOut)
144
+
145
+ # create connections
146
+ self.customContextMenuRequested.connect(self.showMenu)
147
+ self.selectionChanged.connect(self.updateSelectionInfo)
148
+ window = self.window()
149
+ if hasattr(window, 'openFileMonitor'):
150
+ window.styleSheetChanged.connect(self.updateColorScheme)
151
+
152
+ # Create shortcuts
153
+ icon = QIcon(resourcePath('img/content-copy.png'))
154
+
155
+ # We have to re-create the copy shortcut so we can use our implementation
156
+ self.uiCopyACT = QAction(icon, 'Copy', self)
157
+ self.uiCopyACT.setShortcut('Ctrl+C')
158
+ self.uiCopyACT.triggered.connect(self.copy)
159
+ self.addAction(self.uiCopyACT)
160
+
161
+ iconlstrip = QIcon(resourcePath('img/content-duplicate.png'))
162
+ self.uiCopyLstripACT = QAction(iconlstrip, 'Copy lstrip', self)
163
+ self.uiCopyLstripACT.setShortcut('Ctrl+Shift+C')
164
+ self.uiCopyLstripACT.triggered.connect(self.copyLstrip)
165
+ self.addAction(self.uiCopyLstripACT)
166
+
167
+ self.uiCopySpaceIndentationACT = QAction(icon, 'Copy Tabs to Spaces', self)
168
+ self.uiCopySpaceIndentationACT.setShortcut('Ctrl+Shift+Space')
169
+ self.uiCopySpaceIndentationACT.triggered.connect(self.copySpaceIndentation)
170
+ self.addAction(self.uiCopySpaceIndentationACT)
171
+
172
+ # Update keyboard shortcuts that come with QsciScintilla
173
+ commands = self.standardCommands()
174
+ # Remove the Ctrl+/ "Move left one word part" shortcut so it can be used to
175
+ # comment
176
+ command = commands.boundTo(Qt.ControlModifier | Qt.Key_Slash)
177
+ if command is not None:
178
+ command.setKey(0)
179
+
180
+ for command in commands.commands():
181
+ if command.description() == 'Move selected lines up one line':
182
+ command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Up)
183
+ if command.description() == 'Move selected lines down one line':
184
+ command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Down)
185
+ if command.description() == 'Duplicate selection':
186
+ command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_D)
187
+ if command.description() == 'Cut current line':
188
+ command.setKey(0)
189
+
190
+ # Add QShortcuts
191
+ self.uiShowAutoCompleteSCT = QShortcut(
192
+ Qt.CTRL | Qt.Key_Space, self, context=Qt.WidgetShortcut
193
+ )
194
+ self.uiShowAutoCompleteSCT.activated.connect(lambda: self.showAutoComplete())
195
+
196
+ # load the file
197
+ if filename:
198
+ self.load(filename)
199
+ else:
200
+ self.refreshTitle()
201
+ self.setLanguage('Plain Text')
202
+
203
+ # goto the line
204
+ if lineno:
205
+ self.setCursorPosition(lineno, 0)
206
+
207
+ def autoReloadOnChange(self):
208
+ return self._autoReloadOnChange
209
+
210
+ def caretBackgroundColor(self):
211
+ return self._caretBackgroundColor
212
+
213
+ def caretForegroundColor(self):
214
+ return self._caretForegroundColor
215
+
216
+ def setCaretLineBackgroundColor(self, color):
217
+ self._caretBackgroundColor = color
218
+ super(DocumentEditor, self).setCaretLineBackgroundColor(color)
219
+
220
+ def setCaretForegroundColor(self, color):
221
+ self._caretForegroundColor = color
222
+ super(DocumentEditor, self).setCaretForegroundColor(color)
223
+
224
+ def checkForSave(self):
225
+ if self.isModified():
226
+ result = QMessageBox.question(
227
+ self.window(),
228
+ 'Save changes to...',
229
+ 'Do you want to save your changes?',
230
+ QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
231
+ )
232
+ if result == QMessageBox.Yes:
233
+ return self.save()
234
+ elif result == QMessageBox.Cancel:
235
+ return False
236
+ return True
237
+
238
+ def clear(self):
239
+ super(DocumentEditor, self).clear()
240
+ self._filename = ''
241
+
242
+ def closeEvent(self, event):
243
+ self.disableTitleUpdate()
244
+ # unsubcribe the file from the open file monitor
245
+ self.enableFileWatching(False)
246
+ super(DocumentEditor, self).closeEvent(event)
247
+
248
+ def closeEditor(self):
249
+ parent = self.parent()
250
+ if parent and parent.inherits('QMdiSubWindow'):
251
+ parent.close()
252
+
253
+ def commentCheck(self):
254
+ # collect the language
255
+ language = lang.byName(self._language)
256
+ if not language:
257
+ QMessageBox.critical(
258
+ self,
259
+ 'No Language Defined',
260
+ 'There is no language defined for this editor.',
261
+ )
262
+ return '', False
263
+
264
+ # grab the line comment
265
+ comment = language.lineComment()
266
+ if not comment:
267
+ QMessageBox.critical(
268
+ self,
269
+ 'No Line Comment Defined',
270
+ 'There is no line comment symbol defined for the "%s" language.'
271
+ % self._language,
272
+ )
273
+ return '', False
274
+ return comment, True
275
+
276
+ def commentToggle(self, doWhich=None):
277
+ """Toggle comments, mimicing SublimeText functionality.
278
+
279
+ - Comments will be indented to match the outermost line being commented.
280
+ - Commenting / uncommenting is determined by whether all non-empty lines are
281
+ currently commented or not. If they ALL are, then uncomment, otherwise
282
+ comment.
283
+ """
284
+
285
+ # If called by 'triggered' signal, clear out passed argument.
286
+ if not isinstance(doWhich, six.string_types):
287
+ doWhich = None
288
+
289
+ comment, result = self.commentCheck()
290
+ if not result:
291
+ return False
292
+ commentSpace = comment + " "
293
+
294
+ with undo_step(self):
295
+ # lookup the selected text positions
296
+ cursorLine, cursorIndex = self.expandCursorToLineSelection()
297
+ startLine, startCol, endLine, endCol = self.getSelection()
298
+
299
+ # Collect comments and indents, to determine indentation to use, and whether
300
+ # to comment or uncomment.
301
+ comments = []
302
+ indents = []
303
+ for line in range(startLine, endLine + 1):
304
+ lineText = self.getSelectionCurrentLineText(line)
305
+
306
+ # Skip if line is empty, or line is last line without selection
307
+ if not lineText.strip() or (line == endLine and not endCol):
308
+ continue
309
+
310
+ comments.append(lineText.lstrip()[0] == comment)
311
+
312
+ curIndent = self.determineIndent(lineText, comment)
313
+ indents.append(curIndent)
314
+
315
+ if not indents:
316
+ return
317
+ indent = min(indents)
318
+
319
+ # If all lines are comments, we un-comment. If any aren't
320
+ # comments, we comment.
321
+ if doWhich is None:
322
+ if all(comments):
323
+ doWhich = "Uncomment"
324
+ else:
325
+ doWhich = "Comment"
326
+
327
+ for line in range(startLine, endLine + 1):
328
+ lineText = self.getSelectionCurrentLineText(line)
329
+ if not lineText.strip():
330
+ continue
331
+
332
+ # Do not toggle comments on the last line if it contains no selection
333
+ if line != endLine or endCol:
334
+
335
+ if doWhich == "Comment":
336
+ self.setCursorPosition(line, indent)
337
+ self.insert(commentSpace)
338
+ if cursorIndex is not None and cursorIndex >= indent:
339
+ cursorIndex += len(commentSpace)
340
+ if line == startLine:
341
+ startCol -= len(commentSpace)
342
+ if line == endLine:
343
+ endCol += len(commentSpace)
344
+
345
+ elif doWhich == "Uncomment":
346
+ for curComment in [commentSpace, comment]:
347
+ foundText = self.getSelectedCommentText(
348
+ line, indent, len(curComment)
349
+ )
350
+ startCol, endCol, cursorIndex, removed = self.removeComment(
351
+ foundText,
352
+ curComment,
353
+ line,
354
+ indent,
355
+ startLine,
356
+ startCol,
357
+ endLine,
358
+ endCol,
359
+ cursorIndex,
360
+ )
361
+ if removed:
362
+ break
363
+
364
+ # restore the currently selected text, or cursor position
365
+ if cursorLine is not None:
366
+ startLine, endLine = cursorLine, cursorLine
367
+ startCol, endCol = cursorIndex, cursorIndex
368
+ self.setSelection(startLine, startCol, endLine, endCol)
369
+
370
+ def removeComment(
371
+ self,
372
+ text,
373
+ comment,
374
+ line,
375
+ indent,
376
+ startLine,
377
+ startCol,
378
+ endLine,
379
+ endCol,
380
+ cursorIndex,
381
+ ):
382
+ removed = False
383
+ if text == comment:
384
+ commentLen = len(comment)
385
+ self.setSelection(line, indent, line, indent + commentLen)
386
+ self.removeSelectedText()
387
+
388
+ # py3 will throw an error if comparing None, so only compare if cursorIndex
389
+ # is not None
390
+ if cursorIndex is not None and cursorIndex > indent:
391
+ adjustment = None
392
+ for checkIndex in range(commentLen - 1):
393
+ newIndex = indent + checkIndex + 1
394
+ if cursorIndex == newIndex:
395
+ adjustment = checkIndex + 1
396
+ break
397
+ if adjustment is None:
398
+ adjustment = commentLen
399
+ cursorIndex -= adjustment
400
+
401
+ if line == startLine:
402
+ startCol -= commentLen
403
+ if line == endLine:
404
+ endCol -= commentLen
405
+
406
+ removed = True
407
+ return startCol, endCol, cursorIndex, removed
408
+
409
+ def determineIndent(self, lineText, comment=None):
410
+ indent = len(lineText) - len(lineText.lstrip())
411
+ return indent
412
+
413
+ def getSelectedCommentText(self, line, indent, commentLen):
414
+ """Because QScintilla.setSelection automatically strips trailing
415
+ whitespace, we grab the whole rest of the line, then reset it
416
+ to just the length of the currentComment
417
+ """
418
+ self.setSelection(line, indent, line, self.lineLength(line))
419
+ text = self.selectedText()
420
+ if len(text) >= commentLen:
421
+ text = text[:commentLen]
422
+ return text
423
+
424
+ def getSelectionCurrentLineText(self, line):
425
+ lineLength = len(self.text(line).rstrip())
426
+ self.setSelection(line, 0, line, lineLength)
427
+ lineText = self.selectedText()
428
+ return lineText
429
+
430
+ def expandCursorToLineSelection(self):
431
+ line, index = None, None
432
+ if not self.hasSelectedText():
433
+ line, index = self.getCursorPosition()
434
+ self.setSelection(line, 0, line, self.lineLength(line) - 2)
435
+ return line, index
436
+
437
+ def copy(self):
438
+ """Copies the selected text.
439
+
440
+ If copyIndentsAsSpaces and self.indentationsUseTabs() is True it will convert
441
+ any indents to spaces before copying the text.
442
+ """
443
+ if self.copyIndentsAsSpaces and self.indentationsUseTabs():
444
+ self.copySpaceIndentation()
445
+ else:
446
+ super(DocumentEditor, self).copy()
447
+
448
+ def copyFilenameToClipboard(self):
449
+ QApplication.clipboard().setText(self._filename)
450
+
451
+ def copyLineReference(self):
452
+ sel = self.getSelection()
453
+ # Note: getSelection is 0 based like all good code
454
+ if sel[0] == -1 and self._clickPos:
455
+ lines = (self.lineAt(self.mapFromGlobal(self._clickPos)) + 1, -1)
456
+ else:
457
+ end = sel[2]
458
+ if sel[3] == 0:
459
+ # if nothing is selected on the last line, exclude it
460
+ end -= 1
461
+ lines = (sel[0] + 1, end + 1)
462
+ args = {'filename': self.filename(), 'plural': ''}
463
+ if lines[1] == -1 or lines[0] == lines[1]:
464
+ args['line'] = lines[0]
465
+ else:
466
+ args['line'] = '{}-{}'.format(*lines)
467
+ args['plural'] = 's'
468
+ QApplication.clipboard().setText(
469
+ '{filename}: Line{plural} {line}'.format(**args)
470
+ )
471
+
472
+ def copyLstrip(self):
473
+ """Copy's the selected text, but strips off any leading whitespace shared by the
474
+ entire selection.
475
+ """
476
+ start, s, end, e = self.getSelection()
477
+ count = end - start + 1
478
+ self.setSelection(start, 0, end, e)
479
+ txt = self.selectedText()
480
+
481
+ def replacement(match):
482
+ return re.sub('[ \t]', '', match.group(), count=1)
483
+
484
+ # NOTE: Don't use re.M, it does not support mac line endings.
485
+ regex = re.compile('(?:^|\r\n?|\n)[ \t]')
486
+ while len(regex.findall(txt)) == count:
487
+ # We found the same number of leading whitespace as lines of text.
488
+ # This means that it all has leading whitespace that needs removed.
489
+ txt = regex.sub(replacement, txt)
490
+ QApplication.clipboard().setText(txt)
491
+
492
+ def copySpaceIndentation(self):
493
+ """Copy the selected text with any tab indents converted to space indents.
494
+
495
+ If indentationsUseTabs is False it will just copy the text
496
+ """
497
+ txt = self.selectedText()
498
+
499
+ def replacement(match):
500
+ return match.group().replace('\t', ' ' * self.tabWidth())
501
+
502
+ # NOTE: Don't use re.M, it does not support mac line endings.
503
+ ret = re.sub('(?:^|\r\n?|\n)\t+', replacement, txt)
504
+ QApplication.clipboard().setText(ret)
505
+
506
+ def detectEndLine(self, text):
507
+ newlineN = text.find('\n')
508
+ newlineR = text.find('\r')
509
+ if newlineN != -1 and newlineR != -1:
510
+ if newlineN == newlineR + 1:
511
+ # CR LF Windows
512
+ return self.EolWindows
513
+ elif newlineR == newlineN + 1:
514
+ # LF CR ACorn and RISC unsuported
515
+ return self.eolMode()
516
+ if newlineN != -1 and newlineR != -1:
517
+ if newlineN < newlineR:
518
+ # First return is a LF
519
+ return self.EolUnix
520
+ else:
521
+ # first return is a CR
522
+ return self.EolMac
523
+ if newlineN != -1:
524
+ return self.EolUnix
525
+ if sys.platform == 'win32':
526
+ return self.EolWindows
527
+ return self.EolUnix
528
+
529
+ def editPermaHighlight(self):
530
+ text, success = QInputDialog.getText(
531
+ self,
532
+ 'Edit PermaHighlight keywords',
533
+ 'Add keywords separated by a space',
534
+ text=' '.join(self.permaHighlight()),
535
+ )
536
+ if success:
537
+ self.setPermaHighlight(text.split(' '))
538
+
539
+ def enableFileWatching(self, state):
540
+ """Enables/Disables open file change monitoring. If enabled, A dialog will pop
541
+ up when ever the open file is changed externally. If file monitoring is
542
+ disabled in the IDE settings it will be ignored.
543
+
544
+ Returns:
545
+ bool:
546
+ """
547
+ # if file monitoring is enabled and we have a file name then set up the file
548
+ # monitoring
549
+ window = self.window()
550
+ self._fileMonitoringActive = False
551
+ if hasattr(window, 'openFileMonitor'):
552
+ fm = window.openFileMonitor()
553
+ if fm:
554
+ if state:
555
+ fm.addPath(self._filename)
556
+ self._fileMonitoringActive = True
557
+ else:
558
+ fm.removePath(self._filename)
559
+ return self._fileMonitoringActive
560
+
561
+ def disableTitleUpdate(self):
562
+ self.modificationChanged.connect(self.refreshTitle)
563
+
564
+ def enableTitleUpdate(self):
565
+ self.modificationChanged.connect(self.refreshTitle)
566
+
567
+ def eventFilter(self, object, event):
568
+ if event.type() == event.Close and not self.checkForSave():
569
+ event.ignore()
570
+ return True
571
+ return False
572
+
573
+ def exploreDocument(self):
574
+ path = self._filename
575
+ if os.path.isfile(path):
576
+ path = os.path.split(path)[0]
577
+
578
+ if os.path.exists(path):
579
+ osystem.explore(path)
580
+ else:
581
+ QMessageBox.critical(
582
+ self, 'Missing Path', 'Could not find %s' % path.replace('/', '\\')
583
+ )
584
+
585
+ def execStandalone(self):
586
+ if self.save():
587
+ os.startfile(str(self.filename()))
588
+
589
+ def foldMarginColors(self):
590
+ """Returns the fold margin's foreground and background QColor
591
+
592
+ Returns:
593
+ foreground(QColor): The foreground color
594
+ background(QColor): The background color
595
+ """
596
+ return self._foldMarginForegroundColor, self._foldMarginBackgroundColor
597
+
598
+ def setFoldMarginColors(self, foreground, background):
599
+ """Sets the fold margins foreground and background QColor
600
+
601
+ Args:
602
+ foreground(QColor): The forground color of the checkerboard
603
+ background(QColor): The background color of the checkerboard
604
+ """
605
+ self._foldMarginForegroundColor = foreground
606
+ self._foldMarginBackgroundColor = background
607
+ super(DocumentEditor, self).setFoldMarginColors(foreground, background)
608
+
609
+ def goToLine(self, line=None):
610
+ if type(line) != int:
611
+ line, accepted = QInputDialog.getInt(self, 'Line Number', 'Line:')
612
+ else:
613
+ accepted = True
614
+
615
+ if accepted:
616
+ # MH 04/12/11 changed from line + 1 to line - 1 to make the gotoLine dialog
617
+ # go to the correct line.
618
+ self.setCursorPosition(line - 1, 0)
619
+ self.ensureLineVisible(line)
620
+
621
+ def goToDefinition(self, text=None):
622
+ if not text:
623
+ text = self.selectedText()
624
+ if not text:
625
+ text, accepted = QInputDialog.getText(self, 'def Name', 'Name:')
626
+ else:
627
+ accepted = True
628
+ else:
629
+ accepted = True
630
+ if accepted:
631
+ descriptors = lang.byName(self.language()).descriptors()
632
+ docText = self.text()
633
+ for descriptor in descriptors:
634
+ result = descriptor.search(docText)
635
+ while result:
636
+ name = result.group('name')
637
+ if name.startswith(text):
638
+ self.findNext(name, 0)
639
+ return
640
+ result = descriptor.search(docText, result.end())
641
+
642
+ def language(self):
643
+ return self._language
644
+
645
+ def languageChosen(self, action):
646
+ self.setLanguage(action.text())
647
+ self.updateColorScheme()
648
+ self._fileMonitoringActive = False
649
+ window = self.window()
650
+ if hasattr(window, 'uiLanguageDDL'):
651
+ window.uiLanguageDDL.blockSignals(True)
652
+ window.uiLanguageDDL.setCurrentLanguage(action.text())
653
+ window.uiLanguageDDL.blockSignals(False)
654
+
655
+ def lineMarginWidth(self):
656
+ return self.marginWidth(self.SymbolMargin)
657
+
658
+ def load(self, filename):
659
+ filename = str(filename)
660
+ if filename and os.path.exists(filename):
661
+ f = QFile(filename)
662
+ f.open(QFile.ReadOnly)
663
+ text = f.readAll()
664
+ self._textCodec = QTextCodec.codecForUtfText(
665
+ text, QTextCodec.codecForName('UTF-8')
666
+ )
667
+ self.setText(self._textCodec.toUnicode(text))
668
+ f.close()
669
+ self.updateFilename(filename)
670
+ self.enableFileWatching(True)
671
+ self.setEolMode(self.detectEndLine(self.text()))
672
+ return True
673
+ return False
674
+
675
+ def filename(self):
676
+ return self._filename
677
+
678
+ def findNext(self, text, flags):
679
+ re = (flags & SearchOptions.QRegExp) != 0
680
+ cs = (flags & SearchOptions.CaseSensitive) != 0
681
+ wo = (flags & SearchOptions.WholeWords) != 0
682
+ wrap = True
683
+ forward = True
684
+
685
+ result = self.findFirst(text, re, cs, wo, wrap, forward)
686
+
687
+ if not result:
688
+ self.findTextNotFound(text)
689
+
690
+ return result
691
+
692
+ def findPrev(self, text, flags):
693
+ re = (flags & SearchOptions.QRegExp) != 0
694
+ cs = (flags & SearchOptions.CaseSensitive) != 0
695
+ wo = (flags & SearchOptions.WholeWords) != 0
696
+ wrap = True
697
+ forward = False
698
+
699
+ isSelected = self.hasSelectedText()
700
+ result = self.findFirst(text, re, cs, wo, wrap, forward)
701
+ if result and isSelected:
702
+ # If text is selected when finding previous, it will find the currently
703
+ # selected text so do another find.
704
+ result = QsciScintilla.findNext(self)
705
+
706
+ if not result:
707
+ self.findTextNotFound(text)
708
+
709
+ return result
710
+
711
+ def find_simple(self, find_state):
712
+ """Python implementation of QsciScintilla.simpleFind.
713
+
714
+ Args:
715
+ find_state (preditor.scintilla.FindState): A find state used to
716
+ manage the find.
717
+
718
+ https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
719
+ """
720
+ if find_state.start_pos == find_state.end_pos:
721
+ return -1
722
+
723
+ self.SendScintilla(self.SCI_SETTARGETSTART, find_state.start_pos)
724
+ self.SendScintilla(self.SCI_SETTARGETEND, find_state.end_pos)
725
+
726
+ # scintilla can't match unicode strings, even in python 3
727
+ # In python 3 you have to cast it to a bytes object
728
+ expr = bytes(str(find_state.expr).encode("utf-8"))
729
+
730
+ return self.SendScintilla(self.SCI_SEARCHINTARGET, len(expr), expr)
731
+
732
+ def find_text(self, find_state):
733
+ """Finds text in the document without changing the selection.
734
+
735
+ Args:
736
+ find_state (preditor.scintilla.FindState): A find state used to
737
+ manage the find.
738
+
739
+ Based on QsciScintilla.doFind.
740
+ https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
741
+ """
742
+ # Set the search flags
743
+ self.SendScintilla(self.SCI_SETSEARCHFLAGS, find_state.flags)
744
+ # If no end was specified, use the end of the document
745
+ if find_state.end_pos is None:
746
+ find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
747
+
748
+ pos = self.find_simple(find_state)
749
+
750
+ # See if it was found. If not and wraparound is wanted, try again.
751
+ if pos == -1 and find_state.wrap:
752
+ if find_state.forward:
753
+ find_state.start_pos = 0
754
+ if find_state.start_pos_original is None:
755
+ find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
756
+ else:
757
+ find_state.end_pos = find_state.start_pos_original
758
+ else:
759
+ if find_state.start_pos_original is None:
760
+ find_state.start_pos = self.SendScintilla(self.SCI_GETLENGTH)
761
+ else:
762
+ find_state.start_pos = find_state.start_pos_original
763
+ find_state.end_pos = 0
764
+ # Give a indication that we have wrapped
765
+ find_state.wrapped = True
766
+
767
+ pos = self.find_simple(find_state)
768
+
769
+ if pos == -1:
770
+ return -1, 0
771
+
772
+ # It was found.
773
+ target_start = self.SendScintilla(self.SCI_GETTARGETSTART)
774
+ target_end = self.SendScintilla(self.SCI_GETTARGETEND)
775
+
776
+ # Finally adjust the start position so that we don't find the same one again.
777
+ if find_state.forward:
778
+ find_state.start_pos = target_end
779
+ else:
780
+ find_state.start_pos = target_start - 1
781
+ if find_state.start_pos < 0:
782
+ find_state.start_pos = 0
783
+
784
+ return target_start, target_end
785
+
786
+ def find_text_from_cursor(self, find_state):
787
+ """Starting from the current cursor position wrapping around, return all
788
+ matches to the provided find_state.
789
+
790
+ Args:
791
+ find_state (preditor.scintilla.FindState): A find state used to
792
+ manage the find.
793
+ """
794
+ # Start searching from the cursor, wrap past the end and stop where we started
795
+ current_position = self.positionFromLineIndex(*self.getCursorPosition())
796
+ find_state.start_pos = current_position
797
+ find_state.start_pos_original = current_position
798
+
799
+ positions = []
800
+ start, end = self.find_text(find_state)
801
+ while start != -1:
802
+ positions.append((start, end))
803
+ if find_state.wrapped:
804
+ # once we have wrapped, disable wrap
805
+ find_state.wrap = False
806
+ start, end = self.find_text(find_state)
807
+ return positions
808
+
809
+ def findTextNotFound(self, text):
810
+ try:
811
+ # If a number was typed in, ask the user if they wanted to goto that line
812
+ # number.
813
+ line = int(text)
814
+ msg = (
815
+ 'Search string "%s" was not found. \nIt looks like a line number, '
816
+ 'would you like to goto line %i?'
817
+ )
818
+ result = QMessageBox.critical(
819
+ self,
820
+ 'No Text Found',
821
+ msg % (text, line),
822
+ buttons=(QMessageBox.Yes | QMessageBox.No),
823
+ defaultButton=QMessageBox.Yes,
824
+ )
825
+ if result == QMessageBox.Yes:
826
+ self.goToLine(line)
827
+ except ValueError:
828
+ QMessageBox.critical(
829
+ self, 'No Text Found', 'Search string "%s" was not found.' % text
830
+ )
831
+
832
+ def keyPressEvent(self, event):
833
+ key = event.key()
834
+ if key == Qt.Key_Backtab:
835
+ self.unindentSelection()
836
+ elif key == Qt.Key_Escape:
837
+ # Using QShortcut for Escape did not seem to work.
838
+ self.showAutoComplete(True)
839
+ else:
840
+ return QsciScintilla.keyPressEvent(self, event)
841
+
842
+ def keyReleaseEvent(self, event):
843
+ if event.key() == Qt.Key_Menu:
844
+ # Calculate the screen coordinates of the text cursor.
845
+ position = self.positionFromLineIndex(*self.getCursorPosition())
846
+ x = self.SendScintilla(self.SCI_POINTXFROMPOSITION, 0, position)
847
+ y = self.SendScintilla(self.SCI_POINTYFROMPOSITION, 0, position)
848
+ # When using the menu key, show the right click menu at the text
849
+ # cursor, not the mouse cursor, it is not in the correct place.
850
+ self.showMenu(QPoint(x, y))
851
+ else:
852
+ return super(DocumentEditor, self).keyReleaseEvent(event)
853
+
854
+ def initSettings(self, first_time=False):
855
+ """Set/reset settings using the IDE section settings."""
856
+
857
+ # set visibility settings
858
+ self.setAutoIndent(True)
859
+ if first_time:
860
+ self.setIndentationsUseTabs(False)
861
+ self.setTabIndents(True)
862
+ self.setTabWidth(4)
863
+ self.setCaretLineVisible(False)
864
+ self.setShowWhitespaces(False)
865
+ self.setMarginLineNumbers(0, True)
866
+ self.setIndentationGuides(False)
867
+ self.setEolVisibility(False)
868
+ self.setShowSmartHighlighting(True)
869
+ self.setBackspaceUnindents(True)
870
+
871
+ self.setEdgeMode(self.EdgeNone)
872
+
873
+ # set autocompletion settings
874
+ self.setAutoCompletionSource(QsciScintilla.AcsAll)
875
+ self.setAutoCompletionThreshold(3)
876
+
877
+ self.setFont(self.documentFont)
878
+ self.setMarginsFont(self.marginsFont())
879
+ self.setMarginWidth(0, QFontMetrics(self.marginsFont()).width('0000000') + 5)
880
+
881
+ def markerNext(self):
882
+ line, index = self.getCursorPosition()
883
+ newline = self.markerFindNext(line + 1, self.marginMarkerMask(1))
884
+
885
+ # wrap around the document if necessary
886
+ if newline == -1:
887
+ newline = self.markerFindNext(0, self.marginMarkerMask(1))
888
+
889
+ self.setCursorPosition(newline, index)
890
+
891
+ def markerLoad(self, input):
892
+ r"""
893
+ \remarks Takes a list of line numbers and adds a marker to each of them
894
+ in the file.
895
+ """
896
+ for line in input:
897
+ marker = self.markerDefine(self.Circle)
898
+ self.markerAdd(line, marker)
899
+
900
+ def markerToggle(self):
901
+ line, index = self.getCursorPosition()
902
+ markers = self.markersAtLine(line)
903
+ if not markers:
904
+ marker = self.markerDefine(self.Circle)
905
+ self.markerAdd(line, marker)
906
+ else:
907
+ self.markerDelete(line)
908
+
909
+ def marginsFont(self):
910
+ return self._marginsFont
911
+
912
+ def multipleSelection(self):
913
+ """Returns if multiple selection is enabled."""
914
+ return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
915
+
916
+ def multipleSelectionAdditionalSelectionTyping(self):
917
+ """Returns if multiple selection allows additional typing."""
918
+ return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
919
+
920
+ def multipleSelectionMultiPaste(self):
921
+ """Paste into all multiple selections."""
922
+ return self.SendScintilla(self.SCI_GETMULTIPASTE)
923
+
924
+ def paste(self):
925
+ text = QApplication.clipboard().text()
926
+ if text.find('\n') == -1 and text.find('\r') == -1:
927
+ return super(DocumentEditor, self).paste()
928
+
929
+ def repForMode(mode):
930
+ if mode == self.EolWindows:
931
+ return '\r\n'
932
+ elif mode == self.EolUnix:
933
+ return '\n'
934
+ else:
935
+ return '\r'
936
+
937
+ text = text.replace(
938
+ repForMode(self.detectEndLine(text)), repForMode(self.eolMode())
939
+ )
940
+ QApplication.clipboard().setText(text)
941
+ return super(DocumentEditor, self).paste()
942
+
943
+ def permaHighlight(self):
944
+ return self._permaHighlight
945
+
946
+ def setPermaHighlight(self, value):
947
+ if not isinstance(value, list):
948
+ raise TypeError('PermaHighlight must be a list')
949
+
950
+ def refreshToolTip(self):
951
+ # TODO: This will proably be removed once I add a user interface to
952
+ # additionalFilenames.
953
+ toolTip = []
954
+ if self.additionalFilenames:
955
+ toolTip.append('<u><b>Additional Filenames:</b></u>')
956
+ for filename in self.additionalFilenames:
957
+ toolTip.append(filename)
958
+ self.setToolTip('\n<br>'.join(toolTip))
959
+
960
+ def reloadFile(self):
961
+ return self.reloadDialog(
962
+ 'Are you sure you want to reload %s? You will lose all changes'
963
+ % os.path.basename(self.filename())
964
+ )
965
+
966
+ def reloadChange(self):
967
+ """Callback for file monitoring. If a file was modified or deleted this method
968
+ is called when Open File Monitoring is enabled. Returns if the file was updated
969
+ or left open
970
+
971
+ Returns:
972
+ bool:
973
+ """
974
+ logger.debug(
975
+ 'Reload Change called: %0.3f Dialog Shown: %r'
976
+ % (self._saveTimer, self._dialogShown),
977
+ )
978
+ if time.time() - self._saveTimer < 0.5:
979
+ # If we are saving no need to reload the file
980
+ logger.debug('timer has not expired')
981
+ return False
982
+ if not os.path.isfile(self.filename()) and not self._dialogShown:
983
+ logger.debug('The file was deleted')
984
+ # the file was deleted, ask the user if they still want to keep the file in
985
+ # the editor.
986
+ self._dialogShown = True
987
+ result = QMessageBox.question(
988
+ self.window(),
989
+ 'File Removed...',
990
+ 'File: %s has been deleted.\nKeep file in editor?' % self.filename(),
991
+ QMessageBox.Yes,
992
+ QMessageBox.No,
993
+ )
994
+ self._dialogShown = False
995
+ if result == QMessageBox.No:
996
+ logger.debug(
997
+ 'The file was deleted, removing document from editor',
998
+ )
999
+ self.parent().close()
1000
+ return False
1001
+ # TODO: The file no longer exists, and the document should be marked as
1002
+ # changed.
1003
+ logger.debug(
1004
+ 'The file was deleted, But the user left it in the editor',
1005
+ )
1006
+ self.enableFileWatching(False)
1007
+ return True
1008
+ logger.debug('Defaulting to reload message')
1009
+ return self.reloadDialog(
1010
+ 'File: %s has been changed.\nReload from disk?' % self.filename()
1011
+ )
1012
+
1013
+ def reloadDialog(self, message, title='Reload File...'):
1014
+ if not self._dialogShown:
1015
+ self._dialogShown = True
1016
+ if self._autoReloadOnChange or not self.isModified():
1017
+ result = QMessageBox.Yes
1018
+ else:
1019
+ result = QMessageBox.question(
1020
+ self.window(), title, message, QMessageBox.Yes | QMessageBox.No
1021
+ )
1022
+ self._dialogShown = False
1023
+ if result == QMessageBox.Yes:
1024
+ return self.load(self.filename())
1025
+ return False
1026
+
1027
+ def replace(self, text, searchtext=None, all=False):
1028
+ # replace the current text with the inputed text
1029
+ if not searchtext:
1030
+ searchtext = self.selectedText()
1031
+
1032
+ # make sure something is selected
1033
+ if not searchtext:
1034
+ return 0
1035
+
1036
+ with undo_step(self):
1037
+ sel = self.getSelection()
1038
+
1039
+ # replace all of the instances of the text
1040
+ if all:
1041
+ count = self.text().count(searchtext, Qt.CaseInsensitive)
1042
+ found = 0
1043
+ while self.findFirst(searchtext, False, False, False, True, True):
1044
+ if found == count:
1045
+ # replaced all items, exit so we don't get a infinite loop
1046
+ break
1047
+ found += 1
1048
+ super(DocumentEditor, self).replace(text)
1049
+
1050
+ # replace a single instance of the text
1051
+ else:
1052
+ count = 1
1053
+ super(DocumentEditor, self).replace(text)
1054
+
1055
+ self.setSelection(*sel)
1056
+
1057
+ return count
1058
+
1059
+ def setText(self, text):
1060
+ self.blockSignals(True)
1061
+ super(DocumentEditor, self).setText(text)
1062
+ self.blockSignals(False)
1063
+ self.spellCheck(0, None)
1064
+
1065
+ def refreshTitle(self):
1066
+ try:
1067
+ parent = self.parent()
1068
+ if parent and parent.inherits('QMdiSubWindow'):
1069
+ parent.setWindowTitle(self.windowTitle())
1070
+ except RuntimeError:
1071
+ pass
1072
+
1073
+ def save(self):
1074
+ logger.debug(' Saved Called'.center(60, '-'))
1075
+ ret = self.saveAs(self.filename())
1076
+ # If the user has provided additionalFilenames to save, process each of them
1077
+ # without switching the current filename.
1078
+ for filename in self.additionalFilenames:
1079
+ self.saveAs(filename, setFilename=False)
1080
+ return ret
1081
+
1082
+ def saveAs(self, filename='', setFilename=True):
1083
+ logger.debug(' Save As Called '.center(60, '-'))
1084
+ newFile = False
1085
+ if not filename:
1086
+ newFile = True
1087
+ filename = self.filename()
1088
+ filename, extFilter = QtCompat.QFileDialog.getSaveFileName(
1089
+ self.window(), 'Save File as...', filename
1090
+ )
1091
+
1092
+ if filename:
1093
+ self._saveTimer = time.time()
1094
+ # save the file to disk
1095
+ f = QFile(filename)
1096
+ f.open(QFile.WriteOnly)
1097
+ # make sure the file is writeable
1098
+ if f.error() != QFile.NoError:
1099
+ logger.debug('An error occured while saving')
1100
+ QMessageBox.question(
1101
+ self.window(),
1102
+ 'Error saving file...',
1103
+ 'There was a error saving the file. Error Code: %i' % f.error(),
1104
+ QMessageBox.Ok,
1105
+ )
1106
+ f.close()
1107
+ return False
1108
+ # Attempt to save the file using the same codec that it used to display it
1109
+ if self._textCodec:
1110
+ f.write(self._textCodec.fromUnicode(self.text()))
1111
+ else:
1112
+ self.write(f)
1113
+ f.close()
1114
+ # notify that the document was saved
1115
+ self.documentSaved.emit(self, filename)
1116
+
1117
+ # update the file
1118
+ if setFilename:
1119
+ self.updateFilename(filename)
1120
+ if newFile:
1121
+ self.enableFileWatching(True)
1122
+ return True
1123
+ return False
1124
+
1125
+ def selectProjectItem(self):
1126
+ window = self.window()
1127
+ if window:
1128
+ window.selectProjectItem(self.filename())
1129
+
1130
+ def selectionBackgroundColor(self):
1131
+ return self._selectionBackgroundColor
1132
+
1133
+ def setSelectionBackgroundColor(self, color):
1134
+ self._selectionBackgroundColor = color
1135
+ super(DocumentEditor, self).setSelectionBackgroundColor(color)
1136
+
1137
+ def selectionForegroundColor(self):
1138
+ return self._selectionForegroundColor
1139
+
1140
+ def setSelectionForegroundColor(self, color):
1141
+ self._selectionForegroundColor = color
1142
+ super(DocumentEditor, self).setSelectionForegroundColor(color)
1143
+
1144
+ def selection_is_word(self):
1145
+ """Checks if the current selection is a single word.
1146
+
1147
+ Returns:
1148
+ bool: The selected text is a single word.
1149
+ """
1150
+ sel = self.getSelection()
1151
+ start = self.positionFromLineIndex(*sel[:2])
1152
+ end = self.positionFromLineIndex(*sel[2:])
1153
+ return self.is_word(start, end)
1154
+
1155
+ def is_word(self, start, end):
1156
+ """Checks if the text between start and end position is a word
1157
+
1158
+ Args:
1159
+ start (int): Start of text offset index position.
1160
+ end (int): End of text offset index position.
1161
+
1162
+ Returns:
1163
+ bool: The text between the start and end position is a single word.
1164
+ """
1165
+ if start == end:
1166
+ return False
1167
+ # Get the word at the start of selection, if the selection doesn't match
1168
+ # its not a word.
1169
+ start_pos = self.SendScintilla(self.SCI_WORDSTARTPOSITION, start, True)
1170
+ end_pos = self.SendScintilla(self.SCI_WORDENDPOSITION, start, True)
1171
+
1172
+ return start == start_pos and end == end_pos
1173
+
1174
+ def setLanguage(self, language):
1175
+ if language == 'Plain Text':
1176
+ language = ''
1177
+ # grab the language from the lang module if it is a string
1178
+ if type(language) != lang.Language:
1179
+ language = str(language)
1180
+ language = lang.byName(language)
1181
+
1182
+ # collect the language's lexer
1183
+ if language:
1184
+ lexer = language.createLexer(self)
1185
+ self._language = language.name()
1186
+ else:
1187
+ lexer = None
1188
+ self._language = ''
1189
+
1190
+ # set the lexer & init the settings
1191
+ self.setLexer(lexer)
1192
+ self.initSettings()
1193
+
1194
+ # Add language keywords to aspell session dictionary
1195
+ if self.spellCheckEnabled():
1196
+ self.delayable_engine.delayables['spell_check'].reset_session(self)
1197
+
1198
+ def setLexer(self, lexer):
1199
+ font = self.documentFont
1200
+ if lexer:
1201
+ font = lexer.font(0)
1202
+ # Backup values destroyed when we set the lexer
1203
+ marginFont = self.marginsFont()
1204
+ folds = self.contractedFolds()
1205
+ super(DocumentEditor, self).setLexer(lexer)
1206
+ # Restore values destroyed when we set the lexer
1207
+ self.setContractedFolds(folds)
1208
+ self.setMarginsFont(marginFont)
1209
+ self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1210
+ self.setMarginsForegroundColor(self.marginsForegroundColor())
1211
+ self.setFoldMarginColors(*self.foldMarginColors())
1212
+ self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1213
+ self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1214
+ if lexer:
1215
+ lexer.setColor(
1216
+ self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE
1217
+ )
1218
+ lexer.setPaper(
1219
+ self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE
1220
+ )
1221
+ # QSciLexer.wordCharacters is not virtual, or even exposed. This hack allows
1222
+ # custom lexers to define their own wordCharacters
1223
+ if hasattr(lexer, 'wordCharactersOverride'):
1224
+ wordCharacters = lexer.wordCharactersOverride
1225
+ else:
1226
+ # We can't query the lexer for its word characters, but we can query the
1227
+ # document. This ensures the lexer's wordCharacters are used if switching
1228
+ # from a wordCharactersOverride lexer to a lexer that doesn't define custom
1229
+ # wordCharacters.
1230
+ wordCharacters = self.wordCharacters()
1231
+ self.SendScintilla(self.SCI_SETWORDCHARS, wordCharacters.encode('utf8'))
1232
+
1233
+ if lexer:
1234
+ lexer.setFont(font)
1235
+ else:
1236
+ self.setFont(font)
1237
+
1238
+ def setLineMarginWidth(self, width):
1239
+ self.setMarginWidth(self.SymbolMargin, width)
1240
+
1241
+ def setMarginsFont(self, font):
1242
+ super(DocumentEditor, self).setMarginsFont(font)
1243
+ self._marginsFont = font
1244
+
1245
+ def setMultipleSelection(self, state):
1246
+ """Enables or disables multiple selection
1247
+
1248
+ Args:
1249
+ state (bool): Enable or disable multiple selection. When multiple
1250
+ selection is disabled, it is not possible to select multiple
1251
+ ranges by holding down the Ctrl key while dragging with the
1252
+ mouse.
1253
+ """
1254
+ self.SendScintilla(self.SCI_SETMULTIPLESELECTION, state)
1255
+
1256
+ def setMultipleSelectionAdditionalSelectionTyping(self, state):
1257
+ """Enables or disables multiple selection allows additional typing.
1258
+
1259
+ Args:
1260
+ state (bool): Whether typing, new line, cursor left/right/up/down,
1261
+ backspace, delete, home, and end work with multiple selections
1262
+ simultaneously. Also allows selection and word and line
1263
+ deletion commands.
1264
+ """
1265
+ self.SendScintilla(self.SCI_SETADDITIONALSELECTIONTYPING, state)
1266
+
1267
+ def setMultipleSelectionMultiPaste(self, state):
1268
+ """Enables or disables multiple selection allows additional typing.
1269
+
1270
+ Args:
1271
+ state (int): When pasting into multiple selections, the pasted text
1272
+ can go into just the main selection with self.SC_MULTIPASTE_ONCE or
1273
+ into each selection with self.SC_MULTIPASTE_EACH.
1274
+ self.SC_MULTIPASTE_ONCE is the default.
1275
+ """
1276
+ self.SendScintilla(self.SCI_SETMULTIPASTE, state)
1277
+
1278
+ def setSmartHighlightingRegEx(
1279
+ self, exp=r'[ \t\n\r\.,?;:!()\[\]+\-\*\/#@^%$"\\~&{}|=<>\']'
1280
+ ):
1281
+ """Set the regular expression used to control if a selection is considered
1282
+ valid for smart highlighting.
1283
+
1284
+ Args:
1285
+ exp (str):
1286
+ """
1287
+ self._smartHighlightingRegEx = exp
1288
+ self.selectionValidator = re.compile(exp)
1289
+
1290
+ def setShowFolding(self, state):
1291
+ if state:
1292
+ self.setFolding(self.BoxedTreeFoldStyle)
1293
+ else:
1294
+ self.setFolding(self.NoFoldStyle)
1295
+
1296
+ def setShowLineNumbers(self, state):
1297
+ self.setMarginLineNumbers(self.SymbolMargin, state)
1298
+
1299
+ def setShowSmartHighlighting(self, state):
1300
+ self.delayable_engine.set_delayable_enabled('smart_highlight', state)
1301
+
1302
+ def setShowWhitespaces(self, state):
1303
+ if state:
1304
+ self.setWhitespaceVisibility(QsciScintilla.WsVisible)
1305
+ else:
1306
+ self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
1307
+
1308
+ def spellCheckEnabled(self):
1309
+ """Is spellcheck is enabled for this document."""
1310
+ return self.delayable_engine.delayable_enabled('spell_check')
1311
+
1312
+ def setSpellCheckEnabled(self, state):
1313
+ """Enable/disable spellcheck if spellcheck can be enabled.
1314
+ This changes spellcheck for all documents attached to this
1315
+ documents delayable_engine.
1316
+ """
1317
+ self.delayable_engine.set_delayable_enabled('spell_check', state)
1318
+
1319
+ def addWordToDict(self, word):
1320
+ self.__speller__.addtoPersonal(word)
1321
+ self.__speller__.saveAllwords()
1322
+ self.spellCheck(0, None)
1323
+ self.pos += len(word)
1324
+ self.SendScintilla(self.SCI_GOTOPOS, self.pos)
1325
+
1326
+ def correctSpelling(self, action):
1327
+ self.SendScintilla(self.SCI_GOTOPOS, self.pos)
1328
+ self.SendScintilla(self.SCI_SETANCHOR, self.anchor)
1329
+ with undo_step(self):
1330
+ self.SendScintilla(self.SCI_REPLACESEL, action.text())
1331
+
1332
+ def spellCheck(self, start_pos, end_pos):
1333
+ """Check spelling for some text in the document.
1334
+
1335
+ Args:
1336
+ start_pos (int): The document position to start spell checking.
1337
+ end_pos (int): The document position to stop spell checking.
1338
+
1339
+ Returns:
1340
+ int: Returns 0 if spell check is finished. 1 if additional
1341
+ processing is scheduled. 2 if the spell check was canceled
1342
+ because the widget is not visible.
1343
+ """
1344
+ self.delayable_engine.enqueue(self, 'spell_check', start_pos, end_pos)
1345
+
1346
+ def onTextModified(
1347
+ self,
1348
+ pos,
1349
+ mtype,
1350
+ text,
1351
+ length,
1352
+ linesAdded,
1353
+ line,
1354
+ foldNow,
1355
+ foldPrev,
1356
+ token,
1357
+ annotationLinesAdded,
1358
+ ):
1359
+ if self.spellCheckEnabled() and (
1360
+ (mtype & self.SC_MOD_INSERTTEXT) == self.SC_MOD_INSERTTEXT
1361
+ or (mtype & self.SC_MOD_DELETETEXT) == self.SC_MOD_DELETETEXT
1362
+ ):
1363
+ # Only spell-check if text was inserted/deleted
1364
+ line = self.SendScintilla(self.SCI_LINEFROMPOSITION, pos)
1365
+ # More than one line could have been inserted.
1366
+ # If this number is negative it will cause Qt to crash.
1367
+ lines_to_check = line + max(0, linesAdded)
1368
+ self.spellCheck(
1369
+ self.SendScintilla(self.SCI_POSITIONFROMLINE, line),
1370
+ self.SendScintilla(self.SCI_GETLINEENDPOSITION, lines_to_check),
1371
+ )
1372
+
1373
+ def showAutoComplete(self, toggle=False):
1374
+ # if using autoComplete toggle the autoComplete list
1375
+ if self.autoCompletionSource() == QsciScintilla.AcsAll:
1376
+ if self.isListActive(): # is the autoComplete list visible
1377
+ if toggle:
1378
+ self.cancelList() # Close the autoComplete list
1379
+ else:
1380
+ self.autoCompleteFromAll() # Show the autoComplete list
1381
+
1382
+ def showMenu(self, pos, popup=True):
1383
+ menu = QMenu(self)
1384
+ pos = self.mapToGlobal(pos)
1385
+ self._clickPos = pos
1386
+
1387
+ if self.spellCheckEnabled():
1388
+ # Get the word under the mouse and split the word if camelCase
1389
+ point = self.mapFromGlobal(self._clickPos)
1390
+ x = point.x()
1391
+ y = point.y()
1392
+ wordUnderMouse = self.wordAtPoint(point)
1393
+ positionMouse = self.SendScintilla(self.SCI_POSITIONFROMPOINT, x, y)
1394
+ wordStartPosition = self.SendScintilla(
1395
+ self.SCI_WORDSTARTPOSITION, positionMouse, True
1396
+ )
1397
+ spell_check = self.delayable_engine.delayables['spell_check']
1398
+ results = spell_check.chunk_re.findall(
1399
+ self.text(wordStartPosition, wordStartPosition + len(wordUnderMouse))
1400
+ )
1401
+
1402
+ for space, wordChunk in results:
1403
+ camel_case_words = spell_check.camel_case_split(wordChunk)
1404
+ lengthSpace = len(space)
1405
+ for word in camel_case_words:
1406
+ lengthWord = len(word)
1407
+ # Calcualate the actual word start position accounting for any
1408
+ # non-alpha chars word_new_start_position = wordStartPosition +
1409
+ # lengthSpace
1410
+ if (
1411
+ wordStartPosition + lengthSpace <= positionMouse
1412
+ and wordStartPosition + lengthSpace + lengthWord > positionMouse
1413
+ and not any(letter in string.digits for letter in word)
1414
+ and not self.__speller__.check(word)
1415
+ ):
1416
+ # For camelCase words, get the exact word under the mouse
1417
+ self.pos = wordStartPosition + lengthSpace
1418
+ self.anchor = wordStartPosition + lengthSpace + lengthWord
1419
+ # Add spelling suggestions to menu
1420
+ submenu = menu.addMenu(word)
1421
+ submenu.setObjectName('uiSpellCheckMENU')
1422
+ wordSuggestionList = self.__speller__.suggest(word)
1423
+ for wordSuggestion in wordSuggestionList:
1424
+ act = submenu.addAction(wordSuggestion)
1425
+ submenu.triggered.connect(self.correctSpelling)
1426
+ addmenu = menu.addAction('Add %s to dictionary' % word)
1427
+ addmenu.triggered.connect(partial(self.addWordToDict, word))
1428
+ addmenu.setObjectName('uiSpellCheckAddWordACT')
1429
+ menu.addSeparator()
1430
+ break
1431
+ else:
1432
+ wordStartPosition += lengthWord
1433
+ wordStartPosition += lengthSpace
1434
+
1435
+ act = menu.addAction('Goto')
1436
+ # act.setShortcut('Ctrl+G')
1437
+ act.triggered.connect(self.goToLine)
1438
+ act.setIcon(QIcon(resourcePath('img/skip-next-outline.png')))
1439
+ act = menu.addAction('Go to Definition')
1440
+ # act.setShortcut('Ctrl+Shift+G')
1441
+ act.triggered.connect(self.goToDefinition)
1442
+ act.setIcon(QIcon(resourcePath('img/skip-forward-outline.png')))
1443
+ if self.showSmartHighlighting():
1444
+ act = menu.addAction('Edit PermaHighlight')
1445
+ act.setIcon(QIcon(resourcePath('img/marker.png')))
1446
+ act.triggered.connect(self.editPermaHighlight)
1447
+
1448
+ menu.addSeparator()
1449
+
1450
+ act = menu.addAction('Collapse/Expand All')
1451
+ act.triggered.connect(self.toggleFolding)
1452
+ act.setIcon(QIcon(resourcePath('img/plus-minus-variant.png')))
1453
+
1454
+ menu.addSeparator()
1455
+
1456
+ act = menu.addAction('Cut')
1457
+ act.triggered.connect(self.cut)
1458
+ act.setShortcut('Ctrl+X')
1459
+ act.setIcon(QIcon(resourcePath('img/content-cut.png')))
1460
+
1461
+ act = menu.addAction('Copy')
1462
+ act.triggered.connect(self.copy)
1463
+ act.setShortcut('Ctrl+C')
1464
+ act.setIcon(QIcon(resourcePath('img/content-copy.png')))
1465
+
1466
+ copyMenu = menu.addMenu('Advanced Copy')
1467
+
1468
+ # Note: I cant use the actions defined above because they end up getting garbage
1469
+ # collected
1470
+ iconlstrip = QIcon(resourcePath('img/content-duplicate.png'))
1471
+ act = QAction(iconlstrip, 'Copy lstrip', copyMenu)
1472
+ act.setShortcut('Ctrl+Shift+C')
1473
+ act.triggered.connect(self.copyLstrip)
1474
+ copyMenu.addAction(act)
1475
+
1476
+ icon = QIcon(resourcePath('img/content-copy.png'))
1477
+ act = QAction(icon, 'Copy Tabs to Spaces', copyMenu)
1478
+ act.setShortcut('Ctrl+Shift+Space')
1479
+ act.triggered.connect(self.copySpaceIndentation)
1480
+ copyMenu.addAction(act)
1481
+
1482
+ act = menu.addAction('Paste')
1483
+ act.triggered.connect(self.paste)
1484
+ act.setShortcut('Ctrl+V')
1485
+ act.setIcon(QIcon(resourcePath('img/content-paste.png')))
1486
+
1487
+ menu.addSeparator()
1488
+
1489
+ act = menu.addAction('Copy Line Reference')
1490
+ act.triggered.connect(self.copyLineReference)
1491
+ act.setIcon(QIcon(resourcePath('img/content-copy.png')))
1492
+
1493
+ menu.addSeparator()
1494
+
1495
+ act = menu.addAction('Comment Toggle')
1496
+ act.triggered.connect(self.commentToggle)
1497
+ act.setShortcut("Ctrl+/")
1498
+ act.setIcon(QIcon(resourcePath('img/comment-edit.png')))
1499
+
1500
+ menu.addSeparator()
1501
+
1502
+ act = menu.addAction('To Lowercase')
1503
+ act.triggered.connect(self.toLower)
1504
+ # act.setShortcut('Ctrl+L')
1505
+ act.setIcon(QIcon(resourcePath('img/format-letter-case-lower.png')))
1506
+ act = menu.addAction('To Uppercase')
1507
+ act.triggered.connect(self.toUpper)
1508
+ # act.setShortcut('Ctrl+U')
1509
+ act.setIcon(QIcon(resourcePath('img/format-letter-case-upper.png')))
1510
+
1511
+ menu.addSeparator()
1512
+
1513
+ submenu = menu.addMenu('View as...')
1514
+ submenu.setIcon(QIcon(resourcePath('img/eye-check.png')))
1515
+ lg = self.language()
1516
+ act = submenu.addAction('Plain Text')
1517
+ if lg == "":
1518
+ act.setIcon(QIcon(resourcePath('img/check-bold.png')))
1519
+ submenu.addSeparator()
1520
+
1521
+ for language in lang.languages():
1522
+ act = submenu.addAction(language)
1523
+ if language == lg:
1524
+ act.setIcon(QIcon(resourcePath('img/check-bold.png')))
1525
+
1526
+ submenu.triggered.connect(self.languageChosen)
1527
+
1528
+ menu.addSeparator()
1529
+
1530
+ act = menu.addAction('Indent using tabs')
1531
+ act.triggered.connect(self.setIndentationsUseTabs)
1532
+ act.setCheckable(True)
1533
+ act.setChecked(self.indentationsUseTabs())
1534
+
1535
+ if self._fileMonitoringActive:
1536
+ act = menu.addAction('Auto Reload file')
1537
+ act.triggered.connect(self.setAutoReloadOnChange)
1538
+ act.setCheckable(True)
1539
+ act.setChecked(self._autoReloadOnChange)
1540
+
1541
+ if popup:
1542
+ menu.popup(self._clickPos)
1543
+ return menu
1544
+
1545
+ def showEvent(self, event):
1546
+ super(DocumentEditor, self).showEvent(event)
1547
+ # Update the colorScheme after the stylesheet has been fully loaded.
1548
+ self.updateColorScheme()
1549
+
1550
+ def showFolding(self):
1551
+ return self.folding() != self.NoFoldStyle
1552
+
1553
+ def showLineNumbers(self):
1554
+ return self.marginLineNumbers(self.SymbolMargin)
1555
+
1556
+ def showSmartHighlighting(self):
1557
+ return self.delayable_engine.delayable_enabled('smart_highlight')
1558
+
1559
+ def showWhitespaces(self):
1560
+ return self.whitespaceVisibility() == QsciScintilla.WsVisible
1561
+
1562
+ def smartHighlightingRegEx(self):
1563
+ return self._smartHighlightingRegEx
1564
+
1565
+ def toLower(self):
1566
+ with undo_step(self):
1567
+ lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1568
+ text = self.selectedText().lower()
1569
+ self.removeSelectedText()
1570
+ self.insert(text)
1571
+ self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1572
+
1573
+ def toggleFolding(self):
1574
+ self.foldAll(QApplication.instance().keyboardModifiers() == Qt.ShiftModifier)
1575
+
1576
+ def toUpper(self):
1577
+ with undo_step(self):
1578
+ lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1579
+ text = self.selectedText().upper()
1580
+ self.removeSelectedText()
1581
+ self.insert(text)
1582
+ self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1583
+
1584
+ def updateColorScheme(self):
1585
+ """Sets the DocumentEditor's lexer colors, see colorScheme for a compatible
1586
+ dict
1587
+ """
1588
+ # lookup the language
1589
+ language = lang.byName(self.language())
1590
+ lex = self.lexer()
1591
+ if not lex:
1592
+ self.setPaper(self.paperDefault)
1593
+ self.setColor(self.colorDefault)
1594
+ return
1595
+ # Backup the lexer font. The calls to setPaper/setColor cause it to be reset.
1596
+ font = lex.font(0)
1597
+ # Set Default lexer colors
1598
+ for i in range(128):
1599
+ lex.setPaper(self.paperDefault, i)
1600
+ lex.setColor(self.colorDefault, i)
1601
+ lex.setDefaultPaper(self.paperDefault)
1602
+ lex.setDefaultColor(self.colorDefault)
1603
+ # Override lexer color/paper values
1604
+ if language:
1605
+ _lexerColorNames = set(
1606
+ [
1607
+ x.replace('color', '')
1608
+ for x in dir(self)
1609
+ if x.startswith('color') and x.replace('color', '')
1610
+ ]
1611
+ )
1612
+ for colorName, keys in language.lexerColorTypes().items():
1613
+ color = None
1614
+ paper = None
1615
+ if colorName == 'misc':
1616
+ color = self.colorDefault
1617
+ paper = self.paperDefault
1618
+ else:
1619
+ for name in _lexerColorNames:
1620
+ if name.lower() == colorName:
1621
+ color = getattr(self, 'color{}'.format(name))
1622
+ paper = getattr(self, 'paper{}'.format(name))
1623
+ break
1624
+ for key in keys:
1625
+ if paper:
1626
+ lex.setPaper(paper, key)
1627
+ if color:
1628
+ lex.setColor(color, key)
1629
+ lex.setColor(self.braceBadForeground, self.STYLE_BRACEBAD)
1630
+ lex.setPaper(self.braceBadBackground, self.STYLE_BRACEBAD)
1631
+ lex.setColor(self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE)
1632
+ lex.setPaper(self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE)
1633
+ # Update other values stored in the lexer
1634
+ self.setFoldMarginColors(
1635
+ self.foldMarginsForegroundColor, self.foldMarginsBackgroundColor
1636
+ )
1637
+ self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1638
+ self.setMarginsForegroundColor(self.marginsForegroundColor())
1639
+ self.setFoldMarginColors(*self.foldMarginColors())
1640
+ self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1641
+ self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1642
+ # Restore the existing font
1643
+ lex.setFont(font, 0)
1644
+
1645
+ def updateFilename(self, filename):
1646
+ filename = str(filename)
1647
+ extension = os.path.splitext(filename)[1]
1648
+
1649
+ # determine if we need to modify the language
1650
+ if not self._filename or extension != os.path.splitext(self._filename)[1]:
1651
+ self.setLanguage(lang.byExtension(extension))
1652
+
1653
+ # update the filename information
1654
+ self._filename = os.path.abspath(filename)
1655
+ self.setModified(False)
1656
+
1657
+ try:
1658
+ self.window().emitDocumentTitleChanged()
1659
+ except Exception:
1660
+ pass
1661
+
1662
+ self.refreshTitle()
1663
+
1664
+ def updateHighlighter(self):
1665
+ # Get selection
1666
+ selectedText = self.selectedText()
1667
+ # if text is selected make sure it is a word
1668
+ lexer = self.lexer()
1669
+ if selectedText != lexer.highlightedKeywords:
1670
+ if selectedText:
1671
+ validator = self.selectionValidator
1672
+ if hasattr(lexer, 'selectionValidator'):
1673
+ # If a lexer has defined its own selectionValidator use that instead
1674
+ validator = lexer.selectionValidator
1675
+ # Does the text contain a non allowed word?
1676
+ if not validator.findall(selectedText) == []:
1677
+ return
1678
+ else:
1679
+ selection = self.getSelection()
1680
+ # the character before and after the selection must not be a word.
1681
+ text = self.text(selection[2]) # Character after
1682
+ if selection[3] < len(text):
1683
+ if validator.findall(text[selection[3]]) == []:
1684
+ return
1685
+ text = self.text(selection[0]) # Character Before
1686
+ if selection[1] and selection[1] != -1:
1687
+ if validator.findall(text[selection[1] - 1]) == []:
1688
+ return
1689
+
1690
+ def updateSelectionInfo(self):
1691
+ window = self.window()
1692
+ if window and hasattr(window, 'uiCursorInfoLBL'):
1693
+ sline, spos, eline, epos = self.getSelection()
1694
+ # Add 1 to line numbers because document line numbers are 1 based
1695
+ text = ''
1696
+ if sline == -1:
1697
+ line, pos = self.getCursorPosition()
1698
+ line += 1
1699
+ text = 'Line: {} Pos: {}'.format(line, pos)
1700
+ else:
1701
+ sline += 1
1702
+ eline += 1
1703
+ text = (
1704
+ 'Line: {sline} Pos: {spos} To Line: {eline} '
1705
+ 'Pos: {epos} Line Count: {lineCount}'
1706
+ )
1707
+ text = text.format(
1708
+ sline=sline,
1709
+ spos=spos,
1710
+ eline=eline,
1711
+ epos=epos,
1712
+ lineCount=eline - sline + 1,
1713
+ )
1714
+ if self._textCodec and self._textCodec.name() != 'System':
1715
+ text = 'Encoding: {enc} {text}'.format(
1716
+ enc=self._textCodec.name(), text=text
1717
+ )
1718
+ window.uiCursorInfoLBL.setText(text)
1719
+
1720
+ def setAutoReloadOnChange(self, state):
1721
+ self._autoReloadOnChange = state
1722
+
1723
+ def indentSelection(self, all=False):
1724
+ if all:
1725
+ lineFrom = 0
1726
+ lineTo = self.lines()
1727
+ else:
1728
+ lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1729
+ with undo_step(self):
1730
+ for line in range(lineFrom, lineTo + 1):
1731
+ self.indent(line)
1732
+
1733
+ def unindentSelection(self, all=False):
1734
+ if all:
1735
+ lineFrom = 0
1736
+ lineTo = self.lines()
1737
+ else:
1738
+ lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1739
+ with undo_step(self):
1740
+ for line in range(lineFrom, lineTo + 1):
1741
+ self.unindent(line)
1742
+
1743
+ def windowTitle(self):
1744
+ if self._filename:
1745
+ title = os.path.basename(self._filename)
1746
+ else:
1747
+ title = 'New Document'
1748
+
1749
+ if self.isModified():
1750
+ title += '*'
1751
+
1752
+ if self.additionalFilenames:
1753
+ title = '[{}]'.format(title)
1754
+
1755
+ return title
1756
+
1757
+ def wheelEvent(self, event):
1758
+ if self._enableFontResizing and event.modifiers() == Qt.ControlModifier:
1759
+ # If used in LoggerWindow, use that wheel event
1760
+ # May not want to import LoggerWindow, so perhaps
1761
+ # check by str(type())
1762
+ # if isinstance(self.window(), "LoggerWindow"):
1763
+ if "LoggerWindow" in str(type(self.window())):
1764
+ self.window().wheelEvent(event)
1765
+ return
1766
+
1767
+ font = self.documentFont
1768
+ marginsFont = self.marginsFont()
1769
+ lexer = self.lexer()
1770
+ if lexer:
1771
+ font = lexer.font(0)
1772
+ try:
1773
+ # Qt5 support
1774
+ delta = event.angleDelta().y()
1775
+ except Exception:
1776
+ # Qt4 support
1777
+ delta = event.delta()
1778
+ if delta > 0:
1779
+ font.setPointSize(font.pointSize() + 1)
1780
+ marginsFont.setPointSize(marginsFont.pointSize() + 1)
1781
+ else:
1782
+ if font.pointSize() - 1 > 0:
1783
+ font.setPointSize(font.pointSize() - 1)
1784
+ if marginsFont.pointSize() - 1 > 0:
1785
+ marginsFont.setPointSize(marginsFont.pointSize() - 1)
1786
+
1787
+ self.setMarginsFont(marginsFont)
1788
+ if lexer:
1789
+ lexer.setFont(font)
1790
+ else:
1791
+ self.setFont(font)
1792
+
1793
+ self.fontsChanged.emit(font, marginsFont)
1794
+ event.accept()
1795
+ else:
1796
+ super(DocumentEditor, self).wheelEvent(event)
1797
+
1798
+ # expose properties for the designer
1799
+ pyLanguage = Property("QString", language, setLanguage)
1800
+ pyLineMarginWidth = Property("int", lineMarginWidth, setLineMarginWidth)
1801
+ pyShowLineNumbers = Property("bool", showLineNumbers, setShowLineNumbers)
1802
+ pyShowFolding = Property("bool", showFolding, setShowFolding)
1803
+ pyShowSmartHighlighting = Property(
1804
+ "bool", showSmartHighlighting, setShowSmartHighlighting
1805
+ )
1806
+ pySmartHighlightingRegEx = Property(
1807
+ "QString", smartHighlightingRegEx, setSmartHighlightingRegEx
1808
+ )
1809
+
1810
+ pyAutoCompletionCaseSensitivity = Property(
1811
+ "bool",
1812
+ QsciScintilla.autoCompletionCaseSensitivity,
1813
+ QsciScintilla.setAutoCompletionCaseSensitivity,
1814
+ )
1815
+ pyAutoCompletionReplaceWord = Property(
1816
+ "bool",
1817
+ QsciScintilla.autoCompletionReplaceWord,
1818
+ QsciScintilla.setAutoCompletionReplaceWord,
1819
+ )
1820
+ pyAutoCompletionShowSingle = Property(
1821
+ "bool",
1822
+ QsciScintilla.autoCompletionShowSingle,
1823
+ QsciScintilla.setAutoCompletionShowSingle,
1824
+ )
1825
+ pyAutoCompletionThreshold = Property(
1826
+ "int",
1827
+ QsciScintilla.autoCompletionThreshold,
1828
+ QsciScintilla.setAutoCompletionThreshold,
1829
+ )
1830
+ pyAutoIndent = Property(
1831
+ "bool", QsciScintilla.autoIndent, QsciScintilla.setAutoIndent
1832
+ )
1833
+ pyBackspaceUnindents = Property(
1834
+ "bool", QsciScintilla.backspaceUnindents, QsciScintilla.setBackspaceUnindents
1835
+ )
1836
+ pyIndentationGuides = Property(
1837
+ "bool", QsciScintilla.indentationGuides, QsciScintilla.setIndentationGuides
1838
+ )
1839
+ pyIndentationsUseTabs = Property(
1840
+ "bool", QsciScintilla.indentationsUseTabs, QsciScintilla.setIndentationsUseTabs
1841
+ )
1842
+ pyTabIndents = Property(
1843
+ "bool", QsciScintilla.tabIndents, QsciScintilla.setTabIndents
1844
+ )
1845
+ pyUtf8 = Property("bool", QsciScintilla.isUtf8, QsciScintilla.setUtf8)
1846
+ pyWhitespaceVisibility = Property(
1847
+ "bool",
1848
+ QsciScintilla.whitespaceVisibility,
1849
+ QsciScintilla.setWhitespaceVisibility,
1850
+ )
1851
+
1852
+ # Color Setters required because QSci doesn't expose getters.
1853
+ # --------------------------------------------------------------------------------
1854
+ def edgeColor(self):
1855
+ """This is subclassed so we can create a Property of it"""
1856
+ return super(DocumentEditor, self).edgeColor()
1857
+
1858
+ def setEdgeColor(self, color):
1859
+ """This is subclassed so we can create a Property of it"""
1860
+ super(DocumentEditor, self).setEdgeColor(color)
1861
+
1862
+ # Because foreground and background must be set together, this cant use
1863
+ # QtPropertyInit
1864
+ @Property(QColor)
1865
+ def foldMarginsBackgroundColor(self):
1866
+ return self._foldMarginBackgroundColor
1867
+
1868
+ @foldMarginsBackgroundColor.setter
1869
+ def foldMarginsBackgroundColor(self, color):
1870
+ self._foldMarginBackgroundColor = color
1871
+ self.setFoldMarginColors(self._foldMarginForegroundColor, color)
1872
+
1873
+ @Property(QColor)
1874
+ def foldMarginsForegroundColor(self):
1875
+ return self._foldMarginForegroundColor
1876
+
1877
+ @foldMarginsForegroundColor.setter
1878
+ def foldMarginsForegroundColor(self, color):
1879
+ self._foldMarginForegroundColor = color
1880
+ self.setFoldMarginColors(color, self._foldMarginBackgroundColor)
1881
+
1882
+ def indentationGuidesBackgroundColor(self):
1883
+ return self._indentationGuidesBackgroundColor
1884
+
1885
+ def setIndentationGuidesBackgroundColor(self, color):
1886
+ self._indentationGuidesBackgroundColor = color
1887
+ super(DocumentEditor, self).setIndentationGuidesBackgroundColor(color)
1888
+
1889
+ def indentationGuidesForegroundColor(self):
1890
+ return self._indentationGuidesForegroundColor
1891
+
1892
+ def setIndentationGuidesForegroundColor(self, color):
1893
+ self._indentationGuidesForegroundColor = color
1894
+ super(DocumentEditor, self).setIndentationGuidesForegroundColor(color)
1895
+
1896
+ def marginsBackgroundColor(self):
1897
+ return self._marginsBackgroundColor
1898
+
1899
+ def setMarginsBackgroundColor(self, color):
1900
+ self._marginsBackgroundColor = color
1901
+ super(DocumentEditor, self).setMarginsBackgroundColor(color)
1902
+
1903
+ def marginsForegroundColor(self):
1904
+ return self._marginsForegroundColor
1905
+
1906
+ def setMarginsForegroundColor(self, color):
1907
+ self._marginsForegroundColor = color
1908
+ super(DocumentEditor, self).setMarginsForegroundColor(color)
1909
+
1910
+ def matchedBraceBackgroundColor(self):
1911
+ return self._matchedBraceBackgroundColor
1912
+
1913
+ def matchedBraceForegroundColor(self):
1914
+ return self._matchedBraceForegroundColor
1915
+
1916
+ def setMatchedBraceBackgroundColor(self, color):
1917
+ self._matchedBraceBackgroundColor = color
1918
+ super(DocumentEditor, self).setMatchedBraceBackgroundColor(color)
1919
+
1920
+ def setMatchedBraceForegroundColor(self, color):
1921
+ self._matchedBraceForegroundColor = color
1922
+ super(DocumentEditor, self).setMatchedBraceForegroundColor(color)
1923
+
1924
+ def markerBackgroundColor(self):
1925
+ return self._markerBackgroundColor
1926
+
1927
+ def setMarkerBackgroundColor(self, color):
1928
+ self._markerBackgroundColor = color
1929
+ super(DocumentEditor, self).setMarkerBackgroundColor(color)
1930
+
1931
+ def markerForegroundColor(self):
1932
+ return self._markerForegroundColor
1933
+
1934
+ def setMarkerForegroundColor(self, color):
1935
+ self._markerForegroundColor = color
1936
+ super(DocumentEditor, self).setMarkerForegroundColor(color)
1937
+
1938
+ def unmatchedBraceBackgroundColor(self):
1939
+ return self._unmatchedBraceBackgroundColor
1940
+
1941
+ def setUnmatchedBraceBackgroundColor(self, color):
1942
+ self._unmatchedBraceBackgroundColor = color
1943
+ super(DocumentEditor, self).setUnmatchedBraceBackgroundColor(color)
1944
+
1945
+ def unmatchedBraceForegroundColor(self):
1946
+ return self._unmatchedBraceForegroundColor
1947
+
1948
+ def setUnmatchedBraceForegroundColor(self, color):
1949
+ self._unmatchedBraceForegroundColor = color
1950
+ super(DocumentEditor, self).setUnmatchedBraceForegroundColor(color)
1951
+
1952
+ # Handle Stylesheet colors for properties that are built into QsciScintilla but dont
1953
+ # have getters.
1954
+ pyMarginsBackgroundColor = Property(
1955
+ QColor, marginsBackgroundColor, setMarginsBackgroundColor
1956
+ )
1957
+ pyMarginsForegroundColor = Property(
1958
+ QColor, marginsForegroundColor, setMarginsForegroundColor
1959
+ )
1960
+ pyMatchedBraceBackgroundColor = Property(
1961
+ QColor, matchedBraceBackgroundColor, setMatchedBraceBackgroundColor
1962
+ )
1963
+ pyMatchedBraceForegroundColor = Property(
1964
+ QColor, matchedBraceForegroundColor, setMatchedBraceForegroundColor
1965
+ )
1966
+ pyCaretBackgroundColor = Property(
1967
+ QColor, caretBackgroundColor, setCaretLineBackgroundColor
1968
+ )
1969
+ pyCaretForegroundColor = Property(
1970
+ QColor, caretForegroundColor, setCaretForegroundColor
1971
+ )
1972
+ pySelectionBackgroundColor = Property(
1973
+ QColor, selectionBackgroundColor, setSelectionBackgroundColor
1974
+ )
1975
+ pySelectionForegroundColor = Property(
1976
+ QColor, selectionForegroundColor, setSelectionForegroundColor
1977
+ )
1978
+ pyIndentationGuidesBackgroundColor = Property(
1979
+ QColor, indentationGuidesBackgroundColor, setIndentationGuidesBackgroundColor
1980
+ )
1981
+ pyIndentationGuidesForegroundColor = Property(
1982
+ QColor, indentationGuidesForegroundColor, setIndentationGuidesForegroundColor
1983
+ )
1984
+ pyMarkerBackgroundColor = Property(
1985
+ QColor, markerBackgroundColor, setMarkerBackgroundColor
1986
+ )
1987
+ pyMarkerForegroundColor = Property(
1988
+ QColor, markerForegroundColor, setMarkerForegroundColor
1989
+ )
1990
+ pyUnmatchedBraceBackgroundColor = Property(
1991
+ QColor, unmatchedBraceBackgroundColor, setUnmatchedBraceBackgroundColor
1992
+ )
1993
+ pyUnmatchedBraceForegroundColor = Property(
1994
+ QColor, unmatchedBraceForegroundColor, setUnmatchedBraceForegroundColor
1995
+ )
1996
+ pyEdgeColor = Property(QColor, edgeColor, setEdgeColor)
1997
+ documentFont = QtPropertyInit('_documentFont', _defaultFont)
1998
+ pyMarginsFont = Property(QFont, marginsFont, setMarginsFont)
1999
+
2000
+ copyIndentsAsSpaces = QtPropertyInit('_copyIndentsAsSpaces', False)
2001
+
2002
+ # These colors are purely defined in DocumentEditor so we can use QtPropertyInit
2003
+ braceBadForeground = QtPropertyInit('_braceBadForeground', QColor(255, 255, 255))
2004
+ braceBadBackground = QtPropertyInit('_braceBadBackground', QColor(100, 60, 60))
2005
+
2006
+ colorDefault = QtPropertyInit('_colorDefault', QColor())
2007
+ colorComment = QtPropertyInit('_colorComment', QColor(0, 127, 0))
2008
+ colorNumber = QtPropertyInit('_colorNumber', QColor(0, 127, 127))
2009
+ colorString = QtPropertyInit('_colorString', QColor(127, 0, 127))
2010
+ colorKeyword = QtPropertyInit('_colorKeyword', QColor(0, 0, 127))
2011
+ colorTripleQuotedString = QtPropertyInit(
2012
+ '_colorTripleQuotedString', QColor(127, 0, 0)
2013
+ )
2014
+ colorMethod = QtPropertyInit('_colorMethod', QColor(0, 0, 255))
2015
+ colorFunction = QtPropertyInit('_colorFunction', QColor(0, 127, 127))
2016
+ colorOperator = QtPropertyInit('_colorOperator', QColor(0, 0, 0))
2017
+ colorIdentifier = QtPropertyInit('_colorIdentifier', QColor(0, 0, 0))
2018
+ colorCommentBlock = QtPropertyInit('_colorCommentBlock', QColor(127, 127, 127))
2019
+ colorUnclosedString = QtPropertyInit('_colorUnclosedString', QColor(0, 0, 0))
2020
+ colorSmartHighlight = QtPropertyInit('_colorSmartHighlight', QColor(64, 112, 144))
2021
+ colorDecorator = QtPropertyInit('_colorDecorator', QColor(128, 80, 0))
2022
+
2023
+ _defaultPaper = QColor(255, 255, 255)
2024
+ paperDefault = QtPropertyInit('_paperDefault', _defaultPaper)
2025
+ paperComment = QtPropertyInit('_paperComment', _defaultPaper)
2026
+ paperNumber = QtPropertyInit('_paperNumber', _defaultPaper)
2027
+ paperString = QtPropertyInit('_paperString', _defaultPaper)
2028
+ paperKeyword = QtPropertyInit('_paperKeyword', _defaultPaper)
2029
+ paperTripleQuotedString = QtPropertyInit('_paperTripleQuotedString', _defaultPaper)
2030
+ paperMethod = QtPropertyInit('_paperMethod', _defaultPaper)
2031
+ paperFunction = QtPropertyInit('_paperFunction', _defaultPaper)
2032
+ paperOperator = QtPropertyInit('_paperOperator', _defaultPaper)
2033
+ paperIdentifier = QtPropertyInit('_paperIdentifier', _defaultPaper)
2034
+ paperCommentBlock = QtPropertyInit('_paperCommentBlock', _defaultPaper)
2035
+ paperUnclosedString = QtPropertyInit('_paperUnclosedString', QColor(224, 192, 224))
2036
+ paperSmartHighlight = QtPropertyInit(
2037
+ '_paperSmartHighlight', QColor(155, 255, 155, 75)
2038
+ )
2039
+ paperDecorator = QtPropertyInit('_paperDecorator', _defaultPaper)