PrEditor 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. preditor/__init__.py +315 -0
  2. preditor/__main__.py +13 -0
  3. preditor/about_module.py +165 -0
  4. preditor/cli.py +192 -0
  5. preditor/config.py +318 -0
  6. preditor/constants.py +13 -0
  7. preditor/contexts.py +210 -0
  8. preditor/cores/__init__.py +0 -0
  9. preditor/cores/core.py +20 -0
  10. preditor/dccs/.hab.json +10 -0
  11. preditor/dccs/maya/PrEditor_maya.mod +1 -0
  12. preditor/dccs/maya/README.md +22 -0
  13. preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
  14. preditor/dccs/studiomax/PackageContents.xml +32 -0
  15. preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
  16. preditor/dccs/studiomax/README.md +17 -0
  17. preditor/dccs/studiomax/preditor.ms +16 -0
  18. preditor/dccs/studiomax/preditor_menu.mnx +7 -0
  19. preditor/debug.py +149 -0
  20. preditor/delayable_engine/__init__.py +302 -0
  21. preditor/delayable_engine/delayables.py +85 -0
  22. preditor/enum.py +728 -0
  23. preditor/excepthooks.py +165 -0
  24. preditor/gui/__init__.py +56 -0
  25. preditor/gui/app.py +163 -0
  26. preditor/gui/codehighlighter.py +289 -0
  27. preditor/gui/completer.py +237 -0
  28. preditor/gui/console.py +605 -0
  29. preditor/gui/console_base.py +911 -0
  30. preditor/gui/dialog.py +181 -0
  31. preditor/gui/drag_tab_bar.py +625 -0
  32. preditor/gui/editor_chooser.py +57 -0
  33. preditor/gui/errordialog.py +69 -0
  34. preditor/gui/find_files.py +137 -0
  35. preditor/gui/fuzzy_search/__init__.py +0 -0
  36. preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
  37. preditor/gui/group_tab_widget/__init__.py +0 -0
  38. preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
  39. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  40. preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
  41. preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
  42. preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
  43. preditor/gui/level_buttons.py +358 -0
  44. preditor/gui/logger_window_handler.py +77 -0
  45. preditor/gui/logger_window_plugin.py +35 -0
  46. preditor/gui/loggerwindow.py +2405 -0
  47. preditor/gui/newtabwidget.py +69 -0
  48. preditor/gui/output_console.py +11 -0
  49. preditor/gui/qtdesigner/__init__.py +21 -0
  50. preditor/gui/qtdesigner/_log_plugin.py +29 -0
  51. preditor/gui/qtdesigner/console_base_plugin.py +48 -0
  52. preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
  53. preditor/gui/set_text_editor_path_dialog.py +61 -0
  54. preditor/gui/status_label.py +99 -0
  55. preditor/gui/suggest_path_quotes_dialog.py +50 -0
  56. preditor/gui/ui/editor_chooser.ui +93 -0
  57. preditor/gui/ui/errordialog.ui +74 -0
  58. preditor/gui/ui/find_files.ui +140 -0
  59. preditor/gui/ui/loggerwindow.ui +1909 -0
  60. preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
  61. preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
  62. preditor/gui/window.py +161 -0
  63. preditor/gui/workbox_mixin.py +1139 -0
  64. preditor/gui/workbox_text_edit.py +136 -0
  65. preditor/gui/workboxwidget.py +315 -0
  66. preditor/logging_config.py +55 -0
  67. preditor/osystem.py +401 -0
  68. preditor/plugins.py +118 -0
  69. preditor/prefs.py +381 -0
  70. preditor/resource/environment_variables.html +26 -0
  71. preditor/resource/error_mail.html +85 -0
  72. preditor/resource/error_mail_inline.html +41 -0
  73. preditor/resource/img/README.md +17 -0
  74. preditor/resource/img/arrow_forward.png +0 -0
  75. preditor/resource/img/check-bold.png +0 -0
  76. preditor/resource/img/chevron-down.png +0 -0
  77. preditor/resource/img/chevron-up.png +0 -0
  78. preditor/resource/img/close-thick.png +0 -0
  79. preditor/resource/img/comment-edit.png +0 -0
  80. preditor/resource/img/content-copy.png +0 -0
  81. preditor/resource/img/content-cut.png +0 -0
  82. preditor/resource/img/content-duplicate.png +0 -0
  83. preditor/resource/img/content-paste.png +0 -0
  84. preditor/resource/img/content-save.png +0 -0
  85. preditor/resource/img/debug_disabled.png +0 -0
  86. preditor/resource/img/eye-check.png +0 -0
  87. preditor/resource/img/file-plus.png +0 -0
  88. preditor/resource/img/file-remove.png +0 -0
  89. preditor/resource/img/format-align-left.png +0 -0
  90. preditor/resource/img/format-letter-case-lower.png +0 -0
  91. preditor/resource/img/format-letter-case-upper.png +0 -0
  92. preditor/resource/img/format-letter-case.svg +1 -0
  93. preditor/resource/img/information.png +0 -0
  94. preditor/resource/img/logging_critical.png +0 -0
  95. preditor/resource/img/logging_custom.png +0 -0
  96. preditor/resource/img/logging_debug.png +0 -0
  97. preditor/resource/img/logging_error.png +0 -0
  98. preditor/resource/img/logging_info.png +0 -0
  99. preditor/resource/img/logging_not_set.png +0 -0
  100. preditor/resource/img/logging_warning.png +0 -0
  101. preditor/resource/img/marker.png +0 -0
  102. preditor/resource/img/play.png +0 -0
  103. preditor/resource/img/playlist-play.png +0 -0
  104. preditor/resource/img/plus-minus-variant.png +0 -0
  105. preditor/resource/img/preditor.ico +0 -0
  106. preditor/resource/img/preditor.png +0 -0
  107. preditor/resource/img/preditor.psd +0 -0
  108. preditor/resource/img/preditor.svg +44 -0
  109. preditor/resource/img/regex.svg +1 -0
  110. preditor/resource/img/restart.svg +1 -0
  111. preditor/resource/img/skip-forward-outline.png +0 -0
  112. preditor/resource/img/skip-next-outline.png +0 -0
  113. preditor/resource/img/skip-next.png +0 -0
  114. preditor/resource/img/skip-previous.png +0 -0
  115. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  116. preditor/resource/img/text-search-variant.png +0 -0
  117. preditor/resource/img/warning-big.png +0 -0
  118. preditor/resource/lang/python.json +30 -0
  119. preditor/resource/pref_updates/pref_updates.json +17 -0
  120. preditor/resource/settings.ini +25 -0
  121. preditor/resource/stylesheet/Bright.css +76 -0
  122. preditor/resource/stylesheet/Dark.css +210 -0
  123. preditor/scintilla/__init__.py +40 -0
  124. preditor/scintilla/delayables/__init__.py +11 -0
  125. preditor/scintilla/delayables/smart_highlight.py +97 -0
  126. preditor/scintilla/delayables/spell_check.py +174 -0
  127. preditor/scintilla/documenteditor.py +1924 -0
  128. preditor/scintilla/finddialog.py +68 -0
  129. preditor/scintilla/lang/__init__.py +80 -0
  130. preditor/scintilla/lang/config/bash.ini +15 -0
  131. preditor/scintilla/lang/config/batch.ini +14 -0
  132. preditor/scintilla/lang/config/cpp.ini +19 -0
  133. preditor/scintilla/lang/config/css.ini +19 -0
  134. preditor/scintilla/lang/config/eyeonscript.ini +17 -0
  135. preditor/scintilla/lang/config/html.ini +21 -0
  136. preditor/scintilla/lang/config/javascript.ini +24 -0
  137. preditor/scintilla/lang/config/lua.ini +16 -0
  138. preditor/scintilla/lang/config/maxscript.ini +20 -0
  139. preditor/scintilla/lang/config/mel.ini +18 -0
  140. preditor/scintilla/lang/config/mu.ini +22 -0
  141. preditor/scintilla/lang/config/nsi.ini +19 -0
  142. preditor/scintilla/lang/config/perl.ini +19 -0
  143. preditor/scintilla/lang/config/puppet.ini +19 -0
  144. preditor/scintilla/lang/config/python.ini +28 -0
  145. preditor/scintilla/lang/config/ruby.ini +19 -0
  146. preditor/scintilla/lang/config/sql.ini +7 -0
  147. preditor/scintilla/lang/config/xml.ini +21 -0
  148. preditor/scintilla/lang/config/yaml.ini +18 -0
  149. preditor/scintilla/lang/language.py +240 -0
  150. preditor/scintilla/lexers/__init__.py +0 -0
  151. preditor/scintilla/lexers/cpplexer.py +22 -0
  152. preditor/scintilla/lexers/javascriptlexer.py +27 -0
  153. preditor/scintilla/lexers/maxscriptlexer.py +235 -0
  154. preditor/scintilla/lexers/mellexer.py +369 -0
  155. preditor/scintilla/lexers/mulexer.py +33 -0
  156. preditor/scintilla/lexers/pythonlexer.py +42 -0
  157. preditor/scintilla/ui/finddialog.ui +160 -0
  158. preditor/settings.py +71 -0
  159. preditor/stream/__init__.py +72 -0
  160. preditor/stream/console_handler.py +169 -0
  161. preditor/stream/director.py +144 -0
  162. preditor/stream/manager.py +97 -0
  163. preditor/streamhandler_helper.py +46 -0
  164. preditor/utils/__init__.py +191 -0
  165. preditor/utils/call_stack.py +86 -0
  166. preditor/utils/cute.py +106 -0
  167. preditor/utils/stylesheets.py +54 -0
  168. preditor/utils/text_search.py +338 -0
  169. preditor/version.py +34 -0
  170. preditor/weakref.py +363 -0
  171. preditor-2.1.0.dist-info/METADATA +308 -0
  172. preditor-2.1.0.dist-info/RECORD +179 -0
  173. preditor-2.1.0.dist-info/WHEEL +5 -0
  174. preditor-2.1.0.dist-info/entry_points.txt +19 -0
  175. preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
  176. preditor-2.1.0.dist-info/top_level.txt +3 -0
  177. tests/encodings/test_ecoding.py +33 -0
  178. tests/find_files/test_find_files.py +74 -0
  179. tests/ide/test_delayable_engine.py +171 -0
@@ -0,0 +1,165 @@
1
+ from __future__ import absolute_import, print_function
2
+
3
+ import sys
4
+ import traceback
5
+
6
+ from Qt import QtCompat
7
+
8
+ from . import config, plugins, utils
9
+ from .contexts import ErrorReport
10
+ from .weakref import WeakList
11
+
12
+
13
+ class PreditorExceptHook(object):
14
+ """Replacement for `sys.excepthook` that adds error reporting features.
15
+
16
+ This calls each callable in the `preditor.config.excepthooks` list any time
17
+ `sys.excepthook` is called due to an raised exception.
18
+
19
+ If `config.excepthooks` is empty when installing this class, it will
20
+ automatically add `default_excepthooks`. You can disable this by adding `None`
21
+ to the list before this class is initialized.
22
+ """
23
+
24
+ callbacks = WeakList()
25
+ """A list of callback called by `call_callbacks()` if enabled. This can be
26
+ used to notify other widgets of tracebacks. The callback must accept
27
+ `*exc_info` arguments.
28
+ """
29
+
30
+ def __init__(self, base_excepthook=None):
31
+ self.base_excepthook = base_excepthook or sys.__excepthook__
32
+
33
+ # Add the default excepthooks
34
+ if not config.excepthooks and config.excepthooks != [None]:
35
+ config.excepthooks.extend(self.default_excepthooks())
36
+
37
+ def __call__(self, *exc_info):
38
+ """Run when an exception is raised and calls all `config.excepthooks`."""
39
+ try:
40
+ for plugin in config.excepthooks:
41
+ if plugin is None:
42
+ continue
43
+ plugin(*exc_info)
44
+
45
+ # Clear any ErrorReports that were generated by this exception handling
46
+ ErrorReport.clearReports()
47
+ except Exception:
48
+ # When developing for preditor.stream and the console, a exception may
49
+ # prevent showing the traceback normally. This last ditch method prints
50
+ # the traceback to the original stderr stream so it can be debugged.
51
+ # Without this you might get little to no output to work with.
52
+ utils.ShellPrint(True).print_exc("PrEditor excepthooks failed")
53
+
54
+ def default_excepthooks(self):
55
+ """Returns default excepthooks handlers.
56
+
57
+ This includes the `call_base_excepthook` and `ask_to_show_logger` methods.
58
+ They enables showing the excepthook in parent streams and prompting the user
59
+ to show PrEditor when an error happens.
60
+ """
61
+ return [
62
+ self.call_base_excepthook,
63
+ self.call_callbacks,
64
+ self.ask_to_show_logger,
65
+ ]
66
+
67
+ def call_base_excepthook(self, *exc_info):
68
+ """Process `base_excepthook` supplied during object instantiation.
69
+
70
+ This is useful for showing the exception in the original excepthook that
71
+ PrEditor replaced when this class was installed.
72
+
73
+ A newline is printed pre-traceback to ensure the first line of output
74
+ is not printed in-line with the prompt. This also provides visual
75
+ separation between tracebacks, when received consecutively.
76
+ """
77
+ # Print the newline to stderr so it will show up in the same stream as
78
+ # the traceback will be printed to.
79
+ print("", file=sys.stderr)
80
+ try:
81
+ self.base_excepthook(*exc_info)
82
+ except (TypeError, NameError):
83
+ sys.__excepthook__(*exc_info)
84
+
85
+ @classmethod
86
+ def call_callbacks(cls, *exc_info):
87
+ for callback in cls.callbacks:
88
+ try:
89
+ callback(*exc_info)
90
+ except Exception:
91
+ utils.ShellPrint(True).print_exc("PrEditor excepthook callback failed")
92
+
93
+ def ask_to_show_logger(self, *exc_info):
94
+ """Show a dialog asking the user how to handle the error."""
95
+ if config.error_dialog_class is True:
96
+ # Default to the base ErrorDialog class
97
+ from .gui.errordialog import ErrorDialog
98
+
99
+ config.error_dialog_class = ErrorDialog
100
+ elif isinstance(config.error_dialog_class, str):
101
+ # If passed an EntryPoint string load the EntryPoint
102
+ config.error_dialog_class = plugins.from_string(config.error_dialog_class)
103
+
104
+ # Handle cases where we shouldn't ask to show the logger.
105
+ if config.error_dialog_class is None:
106
+ return
107
+ elif not config.error_dialog_class.show_prompt(*exc_info):
108
+ return
109
+
110
+ from .gui.console import ConsolePrEdit
111
+ from .gui.loggerwindow import LoggerWindow
112
+
113
+ instance = LoggerWindow.instance(create=False)
114
+
115
+ if instance:
116
+ # logger reference deleted, fallback and print to console
117
+ if not QtCompat.isValid(instance):
118
+ print("[LoggerWindow] LoggerWindow object has been deleted.")
119
+ # TODO: This seems incorrect, what should it be printing?
120
+ print(traceback)
121
+ return
122
+
123
+ # logger is visible and check if it was minimized on windows
124
+ if instance.isVisible() and not instance.isMinimized():
125
+ if instance.uiAutoPromptCHK.isChecked():
126
+ instance.console().startInputLine()
127
+ return
128
+
129
+ # error already prompted by exception currently being handled
130
+ if ConsolePrEdit._errorPrompted:
131
+ return
132
+
133
+ # Preemptively marking error as "prompted" (handled) to avoid errors
134
+ # from being raised multiple times due to C++ and/or threading error
135
+ # processing.
136
+ try:
137
+ ConsolePrEdit._errorPrompted = True
138
+ errorDialog = config.error_dialog_class(config.root_window())
139
+ errorDialog.setText(exc_info)
140
+ errorDialog.exec_()
141
+
142
+ # interrupted until dialog closed
143
+ finally:
144
+ ConsolePrEdit._errorPrompted = False
145
+
146
+ @classmethod
147
+ def install(cls, force=False):
148
+ """
149
+ Install PrEditor excepthook override, returning previously implemented
150
+ excepthook function.
151
+
152
+ Arguments:
153
+ force (bool): force re-installation of excepthook override when
154
+ already previously implemented.
155
+
156
+ Returns:
157
+ func: pre-override excepthook function
158
+ """
159
+ ErrorReport.enabled = True
160
+ prev_excepthook = sys.excepthook
161
+
162
+ if not isinstance(prev_excepthook, cls) or force:
163
+ sys.excepthook = cls(prev_excepthook)
164
+
165
+ return prev_excepthook
@@ -0,0 +1,56 @@
1
+ import re
2
+
3
+ from Qt.QtGui import QCursor
4
+ from Qt.QtWidgets import QStackedWidget, QToolTip
5
+
6
+ from .dialog import Dialog # noqa: F401
7
+ from .window import Window # noqa: F401
8
+
9
+
10
+ def handleMenuHovered(action):
11
+ """Actions in QMenus which are not descendants of a QToolBar will not show
12
+ their toolTips, because... Reasons?
13
+ """
14
+ # Don't show if it's just the text of the action
15
+ text = re.sub(r"(?<!&)&(?!&)", "", action.text())
16
+ text = text.replace('...', '')
17
+
18
+ if text == action.toolTip():
19
+ text = ''
20
+ else:
21
+ text = action.toolTip()
22
+
23
+ menu = action.parent()
24
+ QToolTip.showText(QCursor.pos(), text, menu)
25
+
26
+
27
+ def loadUi(filename, widget, uiname=''):
28
+ """use's Qt's uic loader to load dynamic interafces onto the inputed widget
29
+
30
+ Args:
31
+ filename (str): The python filename. Its basename will be split off, and a
32
+ ui folder will be added. The file ext will be changed to .ui
33
+ widget (QWidget): The basewidget the ui file will be loaded onto.
34
+ uiname (str, optional): Used instead of the basename. This is useful if
35
+ filename is not the same as the ui file you want to load.
36
+ """
37
+ import os.path
38
+
39
+ from Qt import QtCompat
40
+
41
+ # first, inherit the palette of the parent
42
+ if widget.parent():
43
+ widget.setPalette(widget.parent().palette())
44
+
45
+ if not uiname:
46
+ uiname = os.path.basename(filename).split('.')[0]
47
+
48
+ QtCompat.loadUi(os.path.split(filename)[0] + '/ui/%s.ui' % uiname, widget)
49
+
50
+
51
+ def tab_widget_for_tab(tab_widget):
52
+ """Returns the `QTabWidget` `tab_widget` is parented to or `None`."""
53
+ tab_parent = tab_widget.parent()
54
+ if not isinstance(tab_parent, QStackedWidget):
55
+ return None
56
+ return tab_parent.parent()
preditor/gui/app.py ADDED
@@ -0,0 +1,163 @@
1
+ from __future__ import absolute_import
2
+
3
+ import logging
4
+ import os
5
+
6
+ import Qt
7
+ from Qt.QtGui import QIcon
8
+ from Qt.QtWidgets import QApplication, QDialog, QMainWindow, QSplashScreen
9
+
10
+ from .. import DEFAULT_CORE_NAME, resourcePath, settings
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class App(object):
16
+ """Used to create and configure the QApplication instance.
17
+
18
+ Args:
19
+ name (str, optional): Set the QApplication application name to this value.
20
+ args (list, optional): The arguments used to instantiate the QApplication
21
+ if one isn't already initialized.
22
+ app (QApplication, optional): An instance of a QApplication to use
23
+ instead of creating one.
24
+
25
+ Raises:
26
+ RuntimeError: If DISPLAY is not set when running on linux, or the
27
+ current application isn't a instance of QApplication(ie using a
28
+ QCoreApplication).
29
+ """
30
+
31
+ def __init__(self, name=None, args=None, app=None):
32
+ # Used to track if this instance had to create the QApplication or if it
33
+ # was already created
34
+ self.app_created = False
35
+ # If we made the QApplication, did we call exec_ already?
36
+ self.app_has_exec = False
37
+
38
+ if app is None:
39
+ app = QApplication.instance()
40
+
41
+ self.app = app
42
+
43
+ if not self.app:
44
+ # Check for headless environment's
45
+ if settings.OS_TYPE == 'Linux' and os.environ.get('DISPLAY') is None:
46
+ raise RuntimeError(
47
+ 'The PrEditor gui can not run in a headless environment.'
48
+ )
49
+
50
+ if args is None:
51
+ args = []
52
+ # create a new application
53
+ if self.app is None:
54
+ args.extend(self.dpi_awareness_args())
55
+ self.app = QApplication(args)
56
+ self.app_created = True
57
+ # If we are creating the application, configure it
58
+ self.configure_standalone()
59
+
60
+ if not isinstance(self.app, QApplication):
61
+ raise RuntimeError(
62
+ "PrEditor's gui can only be run using a QApplication instance."
63
+ )
64
+
65
+ if self.app and name:
66
+ # If a application name was passed, update the QApplication's
67
+ # application name.
68
+ self.app.setApplicationName(name)
69
+ self.set_app_id(name)
70
+
71
+ def configure_standalone(self):
72
+ """Update the QApplication for standalone running. Updates the icon and
73
+ sets its style to default_style_name."""
74
+ self.app.setWindowIcon(QIcon(resourcePath('img/preditor.png')))
75
+ self.app.setStyle(self.default_style_name())
76
+
77
+ @staticmethod
78
+ def default_style_name():
79
+ """The default style name used when setting up the QApplication.
80
+
81
+ In Qt4 this is Plastique, in Qt5 this is Fusion.
82
+ """
83
+
84
+ if Qt.IsPyQt4 or Qt.IsPySide:
85
+ return 'Plastique'
86
+ return 'Fusion'
87
+
88
+ @classmethod
89
+ def dpi_awareness_args(cls):
90
+ """On windows sets dpiawareness platform flag to 0 to enable
91
+ per-monitor scaling support in Qt.
92
+
93
+ Returns:
94
+ args: Extend the arguments used to intialize the QApplication.
95
+ """
96
+ if settings.OS_TYPE == "Windows" and (Qt.IsPyQt6 or Qt.IsPyQt5):
97
+ # Make Qt automatically scale based on the monitor the window is
98
+ # currently located.
99
+ return ["--platform", "windows:dpiawareness=0"]
100
+ return []
101
+
102
+ @classmethod
103
+ def root_window(cls):
104
+ """Returns the currently active window. Attempts to find the top level
105
+ QMainWindow or QDialog for the current Qt application.
106
+ """
107
+ inst = QApplication.instance()
108
+ root_window = None
109
+ if inst:
110
+ root_window = inst.activeWindow()
111
+ # Ignore QSplashScreen's, they should never be considered the root window.
112
+ if isinstance(root_window, QSplashScreen):
113
+ root_window = None
114
+
115
+ # If the application does not have focus try to find A top level widget
116
+ # that doesn't have a parent and is a QMainWindow or QDialog
117
+ if root_window is None:
118
+ windows = []
119
+ dialogs = []
120
+
121
+ hasTopLevelWidgets = hasattr(QApplication.instance(), "topLevelWidgets")
122
+ if hasTopLevelWidgets:
123
+ for w in inst.topLevelWidgets():
124
+ if w.parent() is None:
125
+ if isinstance(w, QMainWindow):
126
+ windows.append(w)
127
+ elif isinstance(w, QDialog):
128
+ dialogs.append(w)
129
+ if windows:
130
+ root_window = windows[0]
131
+ elif dialogs:
132
+ root_window = dialogs[0]
133
+
134
+ # grab the root window
135
+ if root_window:
136
+ while root_window.parent():
137
+ parent = root_window.parent()
138
+ if isinstance(parent, QSplashScreen):
139
+ return root_window
140
+ else:
141
+ root_window = parent
142
+ return root_window
143
+
144
+ @classmethod
145
+ def set_app_id(cls, app_id=DEFAULT_CORE_NAME):
146
+ if settings.OS_TYPE == "Windows":
147
+ # Set the app user model id here not in the window class so it doesn't
148
+ # try to set the app id for applications that already set the app id.
149
+ try:
150
+ from casement.app_id import AppId
151
+ except ImportError:
152
+ logger.debug(
153
+ "Unable to configure taskbar grouping, use `pip install "
154
+ "casement` to enable this."
155
+ )
156
+ else:
157
+ AppId.set_for_application(app_id)
158
+
159
+ def start(self):
160
+ """Exec's the QApplication if it hasn't already been started."""
161
+ if self.app_created and self.app and not self.app_has_exec:
162
+ self.app_has_exec = True
163
+ Qt.QtCompat.QApplication.exec_()
@@ -0,0 +1,289 @@
1
+ from __future__ import absolute_import
2
+
3
+ import keyword
4
+ import os
5
+ import re
6
+
7
+ from Qt.QtGui import QColor, QSyntaxHighlighter, QTextCharFormat
8
+
9
+ from .. import resourcePath, utils
10
+
11
+
12
+ class CodeHighlighter(QSyntaxHighlighter):
13
+ def __init__(self, widget, language):
14
+ super(CodeHighlighter, self).__init__(widget)
15
+ self._consoleMode = False
16
+
17
+ self.initHighlightVariables()
18
+ self.setLanguage(language)
19
+
20
+ self.defineHighlightVariables()
21
+
22
+ def initHighlightVariables(self):
23
+ """Initialize the variables which will be used in code highlighting"""
24
+
25
+ # For each call of highlightBlock, keep track of the spans of each highlight, so
26
+ # we can prevent overlapping spans (ie if a string is found, but it's within a
27
+ # comment, do not highlight it).
28
+ self.spans = []
29
+
30
+ self._enabled = True
31
+
32
+ # Language specific lists
33
+ self._comments = []
34
+ self._keywords = []
35
+ self._strings = []
36
+
37
+ # Patterns
38
+ self._commentPattern = None
39
+ self._keywordPattern = None
40
+ self._resultPattern = None
41
+ self._stringsPattern = None
42
+
43
+ # Formats
44
+ self._commentFormat = None
45
+ self._keywordFormat = None
46
+ self._resultFormat = None
47
+ self._stringFormat = None
48
+
49
+ # Colors. These may be overriden by parent colors, which themselves my be
50
+ # overridden by stylesheets (ie Bright.css)
51
+ self._commentColor = QColor(0, 206, 52)
52
+ self._keywordColor = QColor(255, 0, 255)
53
+ self._resultColor = QColor(125, 128, 128)
54
+ self._stringColor = QColor(255, 128, 0)
55
+
56
+ def enabled(self):
57
+ return self._enabled
58
+
59
+ def setEnabled(self, state):
60
+ self._enabled = state
61
+
62
+ def setLanguage(self, lang):
63
+ """Sets the language of the highlighter by loading the json definition"""
64
+ filename = resourcePath('lang/%s.json' % lang.lower())
65
+ if os.path.exists(filename):
66
+ data = utils.Json(filename).load()
67
+ self.setObjectName(data.get('name', ''))
68
+
69
+ self._comments = data.get('comments', [])
70
+ self._strings = data.get('strings', [])
71
+
72
+ # If using python, we can get keywords dynamically, otherwise get them from
73
+ # the language json data.
74
+ if lang.lower() == "python":
75
+ self._keywords = keyword.kwlist
76
+ else:
77
+ self._keywords = data.get('keywords', [])
78
+
79
+ return True
80
+ return False
81
+
82
+ def defineHighlightVariables(self):
83
+ """Define the formats and regex patterns which will be used to highlight
84
+ code."""
85
+ # Define highlight formats
86
+ self.defineCommentFormat()
87
+ self.defineKeywordFormat()
88
+ self.defineResultFormat()
89
+ self.defineStringFormat()
90
+
91
+ # Define highlight regex patterns
92
+ self.defineCommentPattern()
93
+ self.defineKeywordPattern()
94
+ self.defineResultPattern()
95
+ self.defineStringPattern()
96
+
97
+ def highlightBlock(self, text):
98
+ """Highlights the inputed text block based on the rules of this code
99
+ highlighter"""
100
+
101
+ if not self.enabled():
102
+ return
103
+
104
+ # Reset the highlight spans for this text block
105
+ self.spans = []
106
+
107
+ if not self.isConsoleMode() or str(text).startswith('>>>'):
108
+
109
+ # We only have a result pattern if the parent has an attr "outputPrompt", so
110
+ # only proceed if we have been able to define self._resultPattern.
111
+ if self._resultPattern:
112
+ self.highlightText(
113
+ text,
114
+ self._resultPattern,
115
+ self._resultFormat,
116
+ )
117
+
118
+ # Format the strings
119
+ self.highlightText(
120
+ text,
121
+ self._stringPattern,
122
+ self._stringFormat,
123
+ )
124
+
125
+ # format the comments
126
+ self.highlightText(
127
+ text,
128
+ self._commentPattern,
129
+ self._commentFormat,
130
+ )
131
+
132
+ # Format the keywords
133
+ self.highlightText(
134
+ text,
135
+ self._keywordPattern,
136
+ self._keywordFormat,
137
+ )
138
+
139
+ def highlightText(self, text, expr, format):
140
+ """Highlights a text group with an expression and format
141
+
142
+ Args:
143
+ text (str): text to highlight
144
+ expr (re.compile): search parameter
145
+ format (QTextCharFormat): formatting rule
146
+ """
147
+ if expr is None or not text:
148
+ return
149
+
150
+ # highlight all the given matches to the expression in the text
151
+ for match in expr.finditer(text):
152
+ match_span = match.span()
153
+ start, end = match_span
154
+ length = end - start
155
+
156
+ # Determine if the current highlight is within an already determined
157
+ # highlight, if so, let's block it.
158
+ blocked = False
159
+ for span in self.spans:
160
+ if start > span[0] and start < span[-1]:
161
+ blocked = True
162
+ break
163
+
164
+ if not blocked:
165
+ self.setFormat(start, length, format)
166
+
167
+ # Append the current span to self.spans, so we can later block
168
+ # new highlights which should be blocked
169
+ self.spans.append(match_span)
170
+
171
+ def isConsoleMode(self):
172
+ """checks to see if this highlighter is in console mode"""
173
+ return self._consoleMode
174
+
175
+ def setConsoleMode(self, state=False):
176
+ """sets the highlighter to only apply to console strings
177
+ (lines starting with >>>)
178
+ """
179
+ self._consoleMode = state
180
+
181
+ def commentColor(self):
182
+ # Pull the color from the parent if possible because this doesn't support
183
+ # stylesheets
184
+ parent = self.parent()
185
+ if parent and hasattr(parent, 'commentColor'):
186
+ return parent.commentColor
187
+ return self._commentColor
188
+
189
+ def setCommentColor(self, color):
190
+ # set the color for the parent if possible because this doesn't support
191
+ # stylesheets
192
+ parent = self.parent()
193
+ if parent and hasattr(parent, 'commentColor'):
194
+ parent.commentColor = color
195
+ self._commentColor = color
196
+
197
+ def keywordColor(self):
198
+ # pull the color from the parent if possible because this doesn't support
199
+ # stylesheets
200
+ parent = self.parent()
201
+ if parent and hasattr(parent, 'keywordColor'):
202
+ return parent.keywordColor
203
+ return self._keywordColor
204
+
205
+ def setKeywordColor(self, color):
206
+ # set the color for the parent if possible because this doesn't support
207
+ # stylesheets
208
+ parent = self.parent()
209
+ if parent and hasattr(parent, 'keywordColor'):
210
+ parent.keywordColor = color
211
+ self._keywordColor = color
212
+
213
+ def resultColor(self):
214
+ # pull the color from the parent if possible because this doesn't support
215
+ # stylesheets
216
+ parent = self.parent()
217
+ if parent and hasattr(parent, 'resultColor'):
218
+ return parent.resultColor
219
+ return self._resultColor
220
+
221
+ def setResultColor(self, color):
222
+ # set the color for the parent if possible because this doesn't support
223
+ # stylesheets
224
+ parent = self.parent()
225
+ if parent and hasattr(parent, 'resultColor'):
226
+ parent.resultColor = color
227
+ self._resultColor = color
228
+
229
+ def stringColor(self):
230
+ # pull the color from the parent if possible because this doesn't support
231
+ # stylesheets
232
+ parent = self.parent()
233
+ if parent and hasattr(parent, 'stringColor'):
234
+ return parent.stringColor
235
+ return self._stringColor
236
+
237
+ def setStringColor(self, color):
238
+ # set the color for the parent if possible because this doesn't support
239
+ # stylesheets
240
+ parent = self.parent()
241
+ if parent and hasattr(parent, 'stringColor'):
242
+ parent.stringColor = color
243
+ self._stringColor = color
244
+
245
+ def defineCommentFormat(self):
246
+ """Define the comment format based on the comment color"""
247
+ self._commentFormat = QTextCharFormat()
248
+ self._commentFormat.setForeground(self.commentColor())
249
+ self._commentFormat.setFontItalic(True)
250
+
251
+ def defineKeywordFormat(self):
252
+ """Define the keyword format based on the keyword color"""
253
+ self._keywordFormat = QTextCharFormat()
254
+ self._keywordFormat.setForeground(self.keywordColor())
255
+
256
+ def defineResultFormat(self):
257
+ """Define the result format based on the result color"""
258
+ self._resultFormat = QTextCharFormat()
259
+ self._resultFormat.setForeground(self.resultColor())
260
+
261
+ def defineStringFormat(self):
262
+ """Define the string format based on the string color"""
263
+ self._stringFormat = QTextCharFormat()
264
+ self._stringFormat.setForeground(self.stringColor())
265
+
266
+ def defineCommentPattern(self):
267
+ """Define the regex pattern to use for comment"""
268
+ pattern = "|".join(self._comments)
269
+ self._commentPattern = re.compile(pattern)
270
+
271
+ def defineKeywordPattern(self):
272
+ """Define the regex pattern to use for keyword"""
273
+ keywords = [r"\b{}\b".format(word) for word in self._keywords]
274
+ pattern = "|".join(keywords)
275
+ self._keywordPattern = re.compile(pattern)
276
+
277
+ def defineResultPattern(self):
278
+ """Define the regex pattern to use for results"""
279
+ parent = self.parent()
280
+ if parent and hasattr(parent, 'outputPrompt'):
281
+ prompt = parent.outputPrompt()
282
+ pattern = '{}[^\n]*'.format(prompt)
283
+ self._resultPattern = re.compile(pattern)
284
+
285
+ def defineStringPattern(self):
286
+ """Define the regex pattern to use for strings."""
287
+ lst = ["""{0}[^{0}\n]*{0}""".format(st) for st in self._strings]
288
+ pattern = "|".join(lst)
289
+ self._stringPattern = re.compile(pattern)